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.
Files changed (107) hide show
  1. package/README.md +85 -25
  2. package/dist/api.d.ts +25 -5
  3. package/dist/api.js +22 -2
  4. package/dist/commands/diagram.d.ts +1 -0
  5. package/dist/commands/diagram.js +7 -1
  6. package/dist/commands/dump.d.ts +15 -2
  7. package/dist/commands/dump.js +18 -4
  8. package/dist/commands/miro-sync.d.ts +3 -0
  9. package/dist/commands/miro-sync.js +8 -0
  10. package/dist/commands/render.d.ts +4 -0
  11. package/dist/commands/render.js +64 -9
  12. package/dist/commands/schemas.d.ts +2 -2
  13. package/dist/commands/schemas.js +117 -24
  14. package/dist/commands/show.d.ts +1 -0
  15. package/dist/commands/show.js +7 -1
  16. package/dist/commands/spaces.d.ts +3 -1
  17. package/dist/commands/spaces.js +11 -4
  18. package/dist/commands/validate-file.js +20 -5
  19. package/dist/commands/validate.js +54 -21
  20. package/dist/config.d.ts +26 -13
  21. package/dist/config.js +104 -31
  22. package/dist/filter/augment-nodes.d.ts +3 -1
  23. package/dist/filter/augment-nodes.js +12 -7
  24. package/dist/filter/filter-nodes.js +1 -1
  25. package/dist/index.js +51 -35
  26. package/dist/load-space-graph.d.ts +42 -0
  27. package/dist/load-space-graph.js +53 -0
  28. package/dist/plugins/index.js +3 -1
  29. package/dist/plugins/loader.js +4 -4
  30. package/dist/plugins/markdown/extract-content-links.d.ts +23 -0
  31. package/dist/plugins/markdown/extract-content-links.js +123 -0
  32. package/dist/plugins/markdown/index.js +5 -5
  33. package/dist/plugins/markdown/parse-embedded.d.ts +6 -0
  34. package/dist/plugins/markdown/parse-embedded.js +50 -7
  35. package/dist/plugins/markdown/read-space.js +41 -5
  36. package/dist/plugins/markdown/render-directory.d.ts +11 -0
  37. package/dist/plugins/markdown/render-directory.js +130 -0
  38. package/dist/plugins/mermaid/index.d.ts +2 -0
  39. package/dist/plugins/mermaid/index.js +14 -0
  40. package/dist/plugins/{markdown → mermaid}/render-mermaid.js +17 -15
  41. package/dist/{integrations → plugins}/miro/cache.d.ts +1 -1
  42. package/dist/plugins/miro/index.d.ts +2 -0
  43. package/dist/plugins/miro/index.js +27 -0
  44. package/dist/{integrations → plugins}/miro/layout.d.ts +2 -2
  45. package/dist/{integrations → plugins}/miro/layout.js +3 -16
  46. package/dist/{integrations → plugins}/miro/styles.d.ts +2 -2
  47. package/dist/plugins/miro/sync.d.ts +7 -0
  48. package/dist/{integrations → plugins}/miro/sync.js +22 -39
  49. package/dist/plugins/util.d.ts +48 -1
  50. package/dist/plugins/util.js +143 -0
  51. package/dist/read/resolve-graph-edges.d.ts +1 -1
  52. package/dist/read/resolve-graph-edges.js +78 -27
  53. package/dist/read/wikilink-utils.d.ts +17 -0
  54. package/dist/read/wikilink-utils.js +40 -0
  55. package/dist/render/render.d.ts +4 -1
  56. package/dist/render/render.js +20 -16
  57. package/dist/schema/evaluate-rule.js +1 -0
  58. package/dist/schema/meta-schemas.d.ts +13 -0
  59. package/dist/schema/meta-schemas.js +50 -0
  60. package/dist/schema/metadata-contract.d.ts +343 -187
  61. package/dist/schema/metadata-contract.js +68 -30
  62. package/dist/schema/schema.d.ts +17 -9
  63. package/dist/schema/schema.js +283 -200
  64. package/dist/schema/validate-graph.d.ts +2 -1
  65. package/dist/schema/validate-graph.js +40 -23
  66. package/dist/schema/validate-rules.d.ts +5 -1
  67. package/dist/schema/validate-rules.js +20 -1
  68. package/dist/schemas/_knowledge_wiki.json +21 -21
  69. package/dist/schemas/_ost_strict.json +2 -2
  70. package/dist/schemas/_sctx_base.json +2 -2
  71. package/dist/schemas/_strategy_general.json +38 -38
  72. package/dist/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
  73. package/dist/schemas/generated/latest/_structured_context_schema_meta.json +229 -0
  74. package/dist/schemas/knowledge_wiki.json +10 -9
  75. package/dist/schemas/strategy_general.json +15 -14
  76. package/dist/schemas/strict_ost.json +17 -16
  77. package/dist/space-context.d.ts +27 -0
  78. package/dist/space-context.js +46 -0
  79. package/dist/space-graph.d.ts +9 -7
  80. package/dist/space-graph.js +7 -5
  81. package/dist/testing.d.ts +33 -0
  82. package/dist/testing.js +72 -0
  83. package/dist/types.d.ts +101 -8
  84. package/dist/validate.d.ts +15 -3
  85. package/dist/validate.js +55 -20
  86. package/docs/concepts.md +37 -4
  87. package/docs/config.md +10 -0
  88. package/docs/rules.md +49 -22
  89. package/docs/schemas.md +110 -35
  90. package/package.json +13 -8
  91. package/schemas/_knowledge_wiki.json +21 -21
  92. package/schemas/_ost_strict.json +2 -2
  93. package/schemas/_sctx_base.json +2 -2
  94. package/schemas/_strategy_general.json +38 -38
  95. package/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
  96. package/schemas/generated/latest/_structured_context_schema_meta.json +229 -0
  97. package/schemas/knowledge_wiki.json +10 -9
  98. package/schemas/strategy_general.json +15 -14
  99. package/schemas/strict_ost.json +17 -16
  100. package/dist/integrations/miro/sync.d.ts +0 -8
  101. package/dist/schemas/generated/_structured_context_schema_meta.json +0 -191
  102. package/schemas/generated/_structured_context_schema_meta.json +0 -191
  103. /package/dist/plugins/{markdown → mermaid}/render-mermaid.d.ts +0 -0
  104. /package/dist/{integrations → plugins}/miro/cache.js +0 -0
  105. /package/dist/{integrations → plugins}/miro/client.d.ts +0 -0
  106. /package/dist/{integrations → plugins}/miro/client.js +0 -0
  107. /package/dist/{integrations → plugins}/miro/styles.js +0 -0
package/README.md CHANGED
@@ -28,10 +28,13 @@ Content and structure
28
28
  Schema validation errors 0
29
29
  Broken links 1
30
30
  Duplicate keys 0
31
- Rule violations 2
31
+ Rule violations (error) 0
32
+ Rule violations (warning) 1
33
+ Rule violations (info) 1
32
34
  Hierarchy violations 0
33
35
  Orphans (hierarchy nodes - no parent) 1
34
- Ignored during parsing 0
36
+ Unresolved content links 0
37
+ Excluded during parsing 0
35
38
 
36
39
  Orphans (hierarchy nodes - no parent):
37
40
  Solution looking for a problem.md
@@ -40,13 +43,14 @@ Broken links:
40
43
  Steal underpants.md: [[Gnome goals]] → Link target "Gnome goals" in field "related" not found
41
44
 
42
45
  Rule violations:
43
- workflow (1):
44
- Build Death Star.md: Opportunities should contribute to at least one Outcome
45
-
46
- best-practice (1):
47
- High tech juice press.md: Explore multiple candidate solutions (aim for at least three) for an active target opportunity
46
+ WARNING (1):
47
+ Build Death Star.md: Opportunities should contribute to at least one Outcome [workflow: opportunity-has-outcome]
48
+ INFO (1):
49
+ High tech juice press.md: Explore multiple candidate solutions (aim for at least three) for the target opportunity [best-practice: solution-quantity]
48
50
  ```
49
51
 
52
+ Rule violations carry a severity (`error`, `warning` or `info`) that defaults from the rule's category. Only errors fail validation — see [docs/rules.md](docs/rules.md#categories-and-severity).
53
+
50
54
  ## Installation
51
55
 
52
56
  Requires [Bun](https://bun.sh) runtime.
@@ -104,7 +108,7 @@ Schemas define the structure and rules for the entities in a space, allowing cus
104
108
 
105
109
  Several schemas (`strict_ost`, `knowledge_wiki` and `general`) are included. The general (strategy) schema combines a basic vision/mission/goals hierarchy with a hierarchy loosely based on Opportunity Solution Trees. It is intentionally flexible to support rapid initial adoption. The strict OST schema has a narrower scope, and reflects Teresa Torres' specific recommendations for Opportunity Solution Trees more closely. The knowledge wiki schema defines a flatter set of types used for knowledge bases.
106
110
 
107
- structured-context schemas use a metaschema based on JSON Schema Draft-07 that adds a top-level `$metadata` block:
111
+ structured-context schemas use a metaschema based on JSON Schema 2020-12 that adds a top-level `$metadata` block:
108
112
 
109
113
  ```json5
110
114
  "$metadata": {
@@ -179,23 +183,48 @@ Bare wikilink items (`- [[Existing Node]]`) in any embedding section populate a
179
183
 
180
184
  With `fieldOn: "parent"`, embedded child nodes (parsed from a matching heading's list or table) are appended as wikilinks to the parent's `field` array, rather than receiving a `parent` field. This matches schemas where the content model naturally lists children on the parent (e.g. `activity.tasks: ["[[Task A]]"]`).
181
185
 
182
- Metadata is composable across `$ref` graphs:
183
- - `hierarchy`: multiple schema files may define a hierarchy; **last one wins** (root schema overrides partials)
184
- - `aliases` are shallow-merged (later wins)
185
- - `rules` merge by `id`; conflicts error unless the later rule sets `override: true`
186
- - `$metadata.rules` supports `$ref` imports for reusable rule packs
187
- - `relationships` are collected from all files
186
+ `$ref` reuses JSON Schema definitions for validation only; it brings no metadata. Metadata is composed only through explicit imports:
187
+
188
+ ```json5
189
+ "$metadata": { "imports": ["sctx://core/_strategy_general"] }
190
+ ```
191
+
192
+ - An import brings the whole schema's `$metadata`, and is transitive (the imported schema's own imports come too).
193
+ - Merge order: imports in order, depth-first, then the schema's own metadata last.
194
+ - One rule for every key: a duplicate rule `id`, alias, relationship (same `parent`/`type`/`field`) or hierarchy `name` is an error unless the later entry sets `override: true` (for an alias, write the value as `{ "type": "goal", "override": true }`).
195
+ - `hierarchy`: each schema file declares at most one hierarchy, and every imported hierarchy is kept as a separate named hierarchy (see [Multiple hierarchies](#multiple-hierarchies))
196
+ - Each rule's `category` supplies its default severity (`validation` → `error`, `coherence`/`workflow` → `warning`, `best-practice` → `info`); an optional `severity` field overrides it.
188
197
 
189
198
  If no file defines `hierarchy`, hierarchy-specific checks are skipped.
190
199
 
200
+ #### Multiple hierarchies
201
+
202
+ A space can have several hierarchies by importing schemas that each declare one. For example, a root schema might import a work hierarchy (`goal → task`) and a skills hierarchy (`skill_area → skill`).
203
+
204
+ - **Naming**: a hierarchy's `name` defaults to the declaring schema's file stem (e.g. `_skills` for `_skills.json`). Set `"name"` in `$metadata.hierarchy` to override it.
205
+ - **Main hierarchy**: if the root schema declares a hierarchy, it is the main one. If the root declares none and exactly one is imported, that one is the main hierarchy. If several are imported, the root must select one:
206
+
207
+ ```json5
208
+ "$metadata": {
209
+ "imports": ["sctx://_work", "sctx://_skills"],
210
+ "hierarchy": { "$ref": "sctx://_work#/$metadata/hierarchy" }
211
+ }
212
+ ```
213
+
214
+ A root may either declare a hierarchy or select one with `$ref`, not both.
215
+ - **Each type belongs to at most one hierarchy.** A type in two hierarchies is an error. To change an imported hierarchy's levels, declare a hierarchy with the same `name` and `"override": true`.
216
+ - **Validation** (layering, parent types, skip levels, orphans) runs per hierarchy. Relationships can connect types across hierarchies.
217
+ - The main hierarchy drives `space_on_a_page` parsing, hierarchy embedding, `template-sync`, and the default `show`/`diagram`/`render` output. Use `--hierarchy <name>` to render another one.
218
+
191
219
  **Customizing Schemas:**
192
220
  - **Partial schemas**: Files starting with an underscore (like `_sctx_base.json`, `_strategy_general.json`, `_knowledge_wiki.json`) are loaded and used to resolve references (using `$ref`).
193
221
  - **Partials as entity libraries**: Partials can define reusable entity types in `$defs` that composing schemas reference via `$ref`. Bundled partials like `_strategy_general` and `_knowledge_wiki` provide common entity sets for strategy and wiki content.
194
- - **Partials can carry metadata**: Unlike plain JSON Schema, partials may include `$metadata` (hierarchy, aliases, relationships, rules). This makes them self-contained units that bundle both type definitions and behavioral metadata.
195
- - **No-metadata partials**: If a partial has no `$metadata`, prefer `$schema: "http://json-schema.org/draft-07/schema#"` so it validates standalone as plain JSON Schema.
196
- - **Loading priority**: Partial schemas are loaded from both the default schema directory and the directory of your specified target schema.
222
+ - **Partials can carry metadata**: Unlike plain JSON Schema, partials may include `$metadata` (hierarchy, aliases, relationships, rules). A schema gets that metadata only by listing the partial in `$metadata.imports`.
223
+ - **No-metadata partials**: If a partial has no `$metadata`, prefer `$schema: "https://json-schema.org/draft/2020-12/schema"` so it validates standalone as plain JSON Schema.
224
+ - **Loading priority**: Partial schemas are loaded from the default schema directory, configured plugins (`schemas/`), and the directory of your specified target schema.
197
225
  - **Transitive resolution**: `$ref` chains are resolved recursively across files/schemas (including nested `allOf` usage in partials).
198
- - **Unique IDs**: To encourage clean namespacing, local partial schemas **must** have unique `$id`s that do not collide with the default schemas. If a collision is detected, validation will fail with an error.
226
+ - **`$id` namespaces**: All `$id`s use `sctx://`. Bundled schemas are `sctx://core/<name>`, plugin schemas `sctx://<pluginName>/<name>`, and local schemas a single name, `sctx://<name>`. Collisions are errors. Legacy bundled ids (`sctx://<name>`, e.g. `sctx://_sctx_base`) resolve to `sctx://core/` with a deprecation warning until 1.0. See [docs/schemas.md](docs/schemas.md#schema-id-namespaces).
227
+ - **Plugin-contributed schemas**: External plugins can contribute schemas via `<pluginRoot>/schemas/` (or `"sctx": { "schemas": "..." }` in `package.json`). Spaces can reference them using `sctx-plugin/schema.json` or `plugin/schema.json`, or by simple filename if the plugin is declared in `plugins`. Schemas resolve statically without executing plugin JavaScript code.
199
228
 
200
229
  Schema resolution order: space config `schema` > global config `schema`. Schema resolution fails if none is configured.
201
230
 
@@ -216,13 +245,24 @@ Validates markdown files against the JSON schema:
216
245
  - Skips files without frontmatter or without a `type` field
217
246
  - Reports validation results with counts and per-file errors
218
247
 
248
+ ### Render space
249
+
250
+ ```bash
251
+ sctx render <space> <format> [--out <dir-or-file>] [--force] [--filter <view>] [--hierarchy <name>] [--data key=value]
252
+ ```
253
+
254
+ Renders a space in a plugin-provided format. Use `sctx render list [space]` to list available formats.
255
+
256
+ - `markdown.bullets` — Indented bullet list tree (outputs to stdout or `--out <file>`).
257
+ - `markdown.directory` — Markdown space directory with one file per node. Writes to `--out <dir>` (refuses to overwrite existing files unless `--force` is passed). Flat directory by default, or organized into per-type subfolders with `--data folders=type` or when configured with `typeInference.folderMap`. Without `--out`, prints a summary of files to be written.
258
+
219
259
  ### Show space tree
220
260
 
221
261
  ```bash
222
- sctx show <space> [--filter <view-or-expression>]
262
+ sctx show <space> [--filter <view-or-expression>] [--hierarchy <name>]
223
263
  ```
224
264
 
225
- Prints the space as an indented hierarchy tree. Hierarchy roots are listed first, followed by orphans (nodes in the hierarchy but with no resolved parent) and non-hierarchy nodes.
265
+ Prints the space as an indented hierarchy tree. Hierarchy roots are listed first, followed by orphans (nodes in the hierarchy but with no resolved parent) and non-hierarchy nodes. The main hierarchy is shown by default; `--hierarchy <name>` selects another (see [Multiple hierarchies](#multiple-hierarchies)). `diagram` and `render` accept the same option.
226
266
 
227
267
  When a node appears under multiple parents (DAG hierarchy), it is printed in full under its first parent. Subsequent appearances with children show a `(*)` marker indicating the subtree is omitted.
228
268
 
@@ -254,6 +294,7 @@ The WHERE predicate is a [JSONata](https://docs.jsonata.org/overview) expression
254
294
  - **`ancestors[]`** — flat array of ancestor nodes, nearest first, deduplicated. Each entry includes all schema fields of the ancestor node, plus:
255
295
  - `_field` — the edge field name that connects to the ancestor
256
296
  - `_source` — `'hierarchy'` or `'relationship'`
297
+ - `_hierarchy` — the hierarchy name, for hierarchy edges
257
298
  - `_selfRef` — whether the edge is a same-type (self-referential) link
258
299
  - **`descendants[]`** — same structure, for descendant nodes
259
300
 
@@ -304,7 +345,7 @@ SELECT relationships(assumption) WHERE resolvedType='opportunity'
304
345
  ### Generate Mermaid diagram
305
346
 
306
347
  ```bash
307
- sctx diagram <space> [--output path/to/output.mmd]
348
+ sctx diagram <space> [--output path/to/output.mmd] [--hierarchy <name>]
308
349
  ```
309
350
 
310
351
  Generates a Mermaid `graph TD` diagram from validated space nodes:
@@ -321,7 +362,7 @@ sctx schemas show <schema-file> [--mermaid-erd] [--space <name>]
321
362
 
322
363
  Generates a Mermaid Entity Relationship Diagram from a schema:
323
364
  - Shows all entity types and their properties
324
- - Displays parent-child relationships based on hierarchy metadata
365
+ - Displays parent-child relationships based on the levels of each hierarchy
325
366
  - Useful for visualizing schema structure during development
326
367
 
327
368
  Example:
@@ -335,13 +376,26 @@ sctx schemas show strategy_general --mermaid-erd
335
376
  sctx miro-sync <space> [--new-frame <title>] [--dry-run] [--verbose]
336
377
  ```
337
378
 
338
- Syncs space nodes to a Miro board as cards with connectors. Requires `MIRO_TOKEN` env var and `miroBoardId` set in the space's config entry.
379
+ Syncs space nodes to a Miro board as cards with connectors. Requires `MIRO_TOKEN` env var and `boardId` set in the `miro` plugin config for the space.
339
380
 
340
- - `--new-frame <title>` — create a new frame on the board and sync into it; auto-saves the resulting `miroFrameId` back to the config file
381
+ - `--new-frame <title>` — create a new frame on the board and sync into it; auto-saves the resulting `frameId` back to the miro plugin config
341
382
  - `--dry-run` — show what would change without touching Miro
342
383
  - `--verbose` / `-v` — detailed per-card and per-connector output
343
384
 
344
- On subsequent runs, the cached `miroFrameId` is used automatically. Cards are colour-coded by node type and linked by parent→child connectors. A local `.miro-cache/` directory tracks Miro IDs to enable incremental updates.
385
+ Configure the miro plugin in the space's config entry:
386
+
387
+ ```json
388
+ {
389
+ "plugins": {
390
+ "miro": {
391
+ "boardId": "your-board-id",
392
+ "frameId": "your-frame-id"
393
+ }
394
+ }
395
+ }
396
+ ```
397
+
398
+ On subsequent runs, the cached `frameId` is used automatically. Cards are colour-coded by node type and linked by parent→child connectors. A local `.miro-cache/` directory tracks Miro IDs to enable incremental updates.
345
399
 
346
400
  Sync is one-way (OST → Miro) and scoped to a single frame. Only cards and connectors created by this tool within that frame are managed — everything else on the board is left untouched. Card content and connectors are overwritten or recreated to match the markdown source; any edits made directly in Miro to managed cards will be lost on the next sync. Existing card positions are not changed.
347
401
 
@@ -357,6 +411,12 @@ Keeps Obsidian template files in sync with schema examples:
357
411
  - `templatePrefix` in `plugins.markdown` config (default blank) sets a naming convention for templates (`{templatePrefix}{type}.md`). This will be used to check existing filenames, and create new templates with `--create-missing`.
358
412
  - `--dry-run` previews changes without writing files
359
413
 
414
+ ## Programmatic API
415
+
416
+ The package exposes a library entry point at `structured-context/api` for embedding the tooling
417
+ in other applications. See
418
+ [docs/api.md](docs/api.md) for the full reference and usage examples.
419
+
360
420
  ## Development
361
421
 
362
422
  ```bash
package/dist/api.d.ts CHANGED
@@ -3,13 +3,33 @@
3
3
  *
4
4
  * Import from this module to use structured-context as a library:
5
5
  *
6
- * import { validateFile } from 'structured-context/api';
6
+ * import { loadConfig, loadSpaceGraph, validateSpace, validateFile } from 'structured-context/api';
7
7
  * import type { StructuredContextPlugin, PluginContext, ParseResult } from 'structured-context/api';
8
+ *
9
+ * Common patterns:
10
+ *
11
+ * const config = loadConfig();
12
+ * const graph = await loadSpaceGraph('my-space', config, { filter: 'my-view' });
13
+ *
14
+ * // Read once, then assemble a graph and validate from the same read and context:
15
+ * const ctx = createSpaceContext('my-space', config);
16
+ * const readResult = await readSpace(ctx);
17
+ * const graph = await assembleSpaceGraph(ctx, { readResult });
18
+ * const validation = await validateSpace(ctx, { readResult });
8
19
  */
9
20
  export type { AnySchemaObject, SchemaObject, ValidateFunction } from 'ajv';
10
21
  export type { Config, SpaceConfig } from './config';
11
- export type { ParseHook, ParseResult, PluginContext, RenderFormat, RenderHook, RenderOptions, StructuredContextPlugin, TemplateSyncHook, TemplateSyncOptions, } from './plugins/util';
22
+ export { loadConfig, resolveSchema, resolveSchemaPath, setConfigPath } from './config';
23
+ export { PLUGIN_PREFIX } from './constants';
24
+ export { assembleSpaceGraph, loadSpaceGraph } from './load-space-graph';
25
+ export type { ParseHook, ParseResult, PluginContext, RenderFileOutput, RenderFormat, RenderHook, RenderMultiFileOutput, RenderOptions, RenderResult, StructuredContextPlugin, TemplateSyncHook, TemplateSyncOptions, } from './plugins/util';
26
+ export { isRenderMultiFileOutput, resolvePluginDir, resolvePluginSchemasDir, resolveSpacePluginSchemas, } from './plugins/util';
27
+ export { readSpace } from './read/read-space';
28
+ export { buildTargetIndex, formatWikilink, type ParsedWikilink, parseWikilink, wikilinkToTarget, } from './read/wikilink-utils';
12
29
  export type { SharedEmbeddingFields } from './schema/metadata-contract';
13
- export type { SpaceGraph } from './space-graph';
14
- export type { BaseNode, EdgeDefinition, FileNotInSpaceResult, FileValidationResult, HierarchyLevel, ParseIssue, Relationship, SchemaMetadata, SchemaWithMetadata, SpaceContext, SpaceNode, UnresolvedRef, ValidateFileOutput, } from './types';
15
- export { validateFile } from './validate';
30
+ export { bundledSchemasDir, loadSchema, type PluginSchemaSource, setBundledSchemasDir } from './schema/schema';
31
+ export { createSpaceContext, SpaceNotFoundError } from './space-context';
32
+ export { buildSpaceGraph, type SpaceGraph } from './space-graph';
33
+ export type { BaseNode, ContentLink, EdgeDefinition, FileNotInSpaceResult, FileValidationIssue, FileValidationResult, Hierarchy, HierarchyLevel, ParseIssue, ReadSpaceResult, Relationship, ResolvedContentLink, RuleCategory, RuleSeverity, RuleViolation, SchemaMetadata, SchemaWithMetadata, SourceLocation, SpaceContext, SpaceNode, UnresolvedRef, ValidateFileOutput, } from './types';
34
+ export type { ValidationResult } from './validate';
35
+ export { validateFile, validateSpace } from './validate';
package/dist/api.js CHANGED
@@ -3,7 +3,27 @@
3
3
  *
4
4
  * Import from this module to use structured-context as a library:
5
5
  *
6
- * import { validateFile } from 'structured-context/api';
6
+ * import { loadConfig, loadSpaceGraph, validateSpace, validateFile } from 'structured-context/api';
7
7
  * import type { StructuredContextPlugin, PluginContext, ParseResult } from 'structured-context/api';
8
+ *
9
+ * Common patterns:
10
+ *
11
+ * const config = loadConfig();
12
+ * const graph = await loadSpaceGraph('my-space', config, { filter: 'my-view' });
13
+ *
14
+ * // Read once, then assemble a graph and validate from the same read and context:
15
+ * const ctx = createSpaceContext('my-space', config);
16
+ * const readResult = await readSpace(ctx);
17
+ * const graph = await assembleSpaceGraph(ctx, { readResult });
18
+ * const validation = await validateSpace(ctx, { readResult });
8
19
  */
9
- export { validateFile } from './validate';
20
+ export { loadConfig, resolveSchema, resolveSchemaPath, setConfigPath } from './config';
21
+ export { PLUGIN_PREFIX } from './constants';
22
+ export { assembleSpaceGraph, loadSpaceGraph } from './load-space-graph';
23
+ export { isRenderMultiFileOutput, resolvePluginDir, resolvePluginSchemasDir, resolveSpacePluginSchemas, } from './plugins/util';
24
+ export { readSpace } from './read/read-space';
25
+ export { buildTargetIndex, formatWikilink, parseWikilink, wikilinkToTarget, } from './read/wikilink-utils';
26
+ export { bundledSchemasDir, loadSchema, setBundledSchemasDir } from './schema/schema';
27
+ export { createSpaceContext, SpaceNotFoundError } from './space-context';
28
+ export { buildSpaceGraph } from './space-graph';
29
+ export { validateFile, validateSpace } from './validate';
@@ -2,4 +2,5 @@ import type { SpaceContext } from '../types';
2
2
  export declare function diagram(context: SpaceContext, options: {
3
3
  output?: string;
4
4
  filter?: string;
5
+ hierarchy?: string;
5
6
  }): Promise<void>;
@@ -1,7 +1,13 @@
1
1
  import { writeFileSync } from 'node:fs';
2
2
  import { executeRender } from '../render/render';
3
3
  export async function diagram(context, options) {
4
- const result = await executeRender('markdown.mermaid', context, { filter: options.filter });
4
+ const result = await executeRender('mermaid.graph', context, {
5
+ filter: options.filter,
6
+ hierarchy: options.hierarchy,
7
+ });
8
+ if (typeof result !== 'string') {
9
+ throw new Error('Expected string output from mermaid diagram renderer');
10
+ }
5
11
  if (options.output) {
6
12
  writeFileSync(options.output, result);
7
13
  console.log(`Mermaid diagram written to ${options.output}`);
@@ -1,2 +1,15 @@
1
- import type { SpaceContext } from '../types';
2
- export declare function dump(context: SpaceContext): Promise<void>;
1
+ import type { ReadSpaceResult, SpaceContext } from '../types';
2
+ export interface DumpOptions {
3
+ /** Named view (resolved via space.views) or a raw filter DSL expression. */
4
+ filter?: string;
5
+ /** Hierarchy used when evaluating the filter (default: the main hierarchy). */
6
+ hierarchy?: string;
7
+ }
8
+ /**
9
+ * Build the dump payload: every parsed node, or only nodes matched by a filter.
10
+ *
11
+ * Unfiltered dumps include schema-invalid nodes, since dump is a debugging aid.
12
+ * Filtered dumps match show/render: the filter runs over the graph of schema-valid nodes.
13
+ */
14
+ export declare function buildDump(context: SpaceContext, options?: DumpOptions): Promise<ReadSpaceResult>;
15
+ export declare function dump(context: SpaceContext, options?: DumpOptions): Promise<void>;
@@ -1,6 +1,20 @@
1
- import JSON5 from 'json5';
1
+ import { assembleSpaceGraph } from '../load-space-graph';
2
2
  import { readSpace } from '../read/read-space';
3
- export async function dump(context) {
4
- const { nodes, source, diagnostics } = await readSpace(context);
5
- console.log(JSON5.stringify({ nodes, source, diagnostics }, null, 2));
3
+ /**
4
+ * Build the dump payload: every parsed node, or only nodes matched by a filter.
5
+ *
6
+ * Unfiltered dumps include schema-invalid nodes, since dump is a debugging aid.
7
+ * Filtered dumps match show/render: the filter runs over the graph of schema-valid nodes.
8
+ */
9
+ export async function buildDump(context, options = {}) {
10
+ const readResult = await readSpace(context);
11
+ if (!options.filter)
12
+ return readResult;
13
+ const graph = await assembleSpaceGraph(context, { ...options, readResult });
14
+ const nodes = readResult.nodes.filter((node) => graph.nodes.get(node.title) === node);
15
+ return { ...readResult, nodes };
16
+ }
17
+ export async function dump(context, options = {}) {
18
+ const { nodes, source, diagnostics } = await buildDump(context, options);
19
+ console.log(JSON.stringify({ nodes, source, diagnostics }, null, 2));
6
20
  }
@@ -0,0 +1,3 @@
1
+ import type { SyncOptions } from '../plugins/miro/sync';
2
+ import type { SpaceContext } from '../types';
3
+ export declare function miroSyncCommand(context: SpaceContext, options: SyncOptions): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import { executeRender } from '../render/render';
2
+ export async function miroSyncCommand(context, options) {
3
+ const summary = await executeRender('sctx-miro:board', context, {
4
+ filter: undefined,
5
+ data: options,
6
+ });
7
+ console.log(summary);
8
+ }
@@ -1,6 +1,10 @@
1
1
  import type { SpaceContext } from '../types';
2
2
  export declare function render(context: SpaceContext, format: string, options: {
3
3
  filter?: string;
4
+ hierarchy?: string;
4
5
  output?: string;
6
+ out?: string;
7
+ force?: boolean;
8
+ data?: Record<string, unknown>;
5
9
  }): Promise<void>;
6
10
  export declare function renderList(context?: SpaceContext): Promise<void>;
@@ -1,17 +1,72 @@
1
- import { writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
2
3
  import { discoverPlugins, loadPlugins } from '../plugins/loader';
4
+ import { isRenderMultiFileOutput } from '../plugins/util';
3
5
  import { buildFormatRegistry } from '../render/registry';
4
6
  import { executeRender } from '../render/render';
5
7
  export async function render(context, format, options) {
6
- const result = await executeRender(format, context, { filter: options.filter });
7
- if (options.output) {
8
- writeFileSync(options.output, result);
9
- console.error(`Written to ${options.output}`);
8
+ const result = await executeRender(format, context, {
9
+ filter: options.filter,
10
+ hierarchy: options.hierarchy,
11
+ data: options.data,
12
+ });
13
+ const targetPath = options.out ?? options.output;
14
+ const force = Boolean(options.force);
15
+ if (typeof result === 'string') {
16
+ if (targetPath) {
17
+ const resolved = resolve(targetPath);
18
+ if (existsSync(resolved)) {
19
+ if (statSync(resolved).isDirectory()) {
20
+ throw new Error(`Output path "${targetPath}" is a directory. Please specify a file path.`);
21
+ }
22
+ if (!force) {
23
+ throw new Error(`File "${targetPath}" already exists. Use --force to overwrite.`);
24
+ }
25
+ }
26
+ mkdirSync(dirname(resolved), { recursive: true });
27
+ writeFileSync(resolved, result, 'utf8');
28
+ console.error(`Written to ${targetPath}`);
29
+ }
30
+ else {
31
+ process.stdout.write(result);
32
+ if (!result.endsWith('\n'))
33
+ process.stdout.write('\n');
34
+ }
35
+ return;
10
36
  }
11
- else {
12
- process.stdout.write(result);
13
- if (!result.endsWith('\n'))
14
- process.stdout.write('\n');
37
+ if (isRenderMultiFileOutput(result)) {
38
+ if (targetPath) {
39
+ const targetDir = resolve(targetPath);
40
+ const conflicts = [];
41
+ for (const file of result.files) {
42
+ const fullPath = join(targetDir, file.path);
43
+ if (existsSync(fullPath)) {
44
+ conflicts.push(file.path);
45
+ }
46
+ }
47
+ if (conflicts.length > 0 && !force) {
48
+ const preview = conflicts
49
+ .slice(0, 5)
50
+ .map((f) => ` ${f}`)
51
+ .join('\n');
52
+ const extra = conflicts.length > 5 ? `\n ... and ${conflicts.length - 5} more` : '';
53
+ throw new Error(`Refusing to overwrite ${conflicts.length} existing file(s) in "${targetPath}" without --force:\n${preview}${extra}`);
54
+ }
55
+ for (const file of result.files) {
56
+ const fullPath = join(targetDir, file.path);
57
+ mkdirSync(dirname(fullPath), { recursive: true });
58
+ writeFileSync(fullPath, file.content, 'utf8');
59
+ }
60
+ console.error(`Written ${result.files.length} file(s) to ${targetPath}`);
61
+ }
62
+ else {
63
+ console.log(`Rendered ${result.files.length} file(s):`);
64
+ for (const file of result.files) {
65
+ const bytes = Buffer.byteLength(file.content, 'utf8');
66
+ console.log(` ${file.path} (${bytes} bytes)`);
67
+ }
68
+ console.log('\nSpecify --out <dir> to write files to disk.');
69
+ }
15
70
  }
16
71
  }
17
72
  export async function renderList(context) {
@@ -1,6 +1,6 @@
1
- export declare function listSchemas(): void;
1
+ export declare function listSchemas(): Promise<void>;
2
2
  export declare function showSchema(file: string | undefined, options: {
3
3
  space?: string;
4
4
  raw: boolean;
5
5
  mermaidErd?: boolean;
6
- }): void;
6
+ }): Promise<void>;