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/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # structured-context
2
+
3
+ Tools for working with Opportunity Solution Tree structures and other product management and strategy frameworks
4
+
5
+ ## Installation
6
+
7
+ Requires [Bun](https://bun.sh) runtime.
8
+
9
+ ```bash
10
+ bun install -g structured-context
11
+ ```
12
+
13
+ Or use directly via `bunx`:
14
+
15
+ ```bash
16
+ bunx structured-context validate <space>
17
+ ```
18
+
19
+ ## Setup for AI Agents
20
+
21
+ A Claude Code plugin is included at `plugin/`. It provides validation hooks, slash commands, and agent skills. Install it with:
22
+
23
+ ```
24
+ claude plugin install mindsocket/structured-context
25
+ ```
26
+
27
+ Skills can also be installed standalone without the plugin:
28
+
29
+ ```
30
+ npx skills add https://github.com/mindsocket/structured-context/tree/main/plugin/skills/structured-context
31
+ ```
32
+
33
+ ## Concepts
34
+
35
+ See [docs/concepts.md](docs/concepts.md) for the full terminology reference, including definitions of nodes, embedded nodes, spaces, schemas, rules, and more.
36
+
37
+ ## Configuration
38
+
39
+ `structured-context` looks for its config file in this order:
40
+
41
+ 1. `$SCTX_CONFIG` — explicit path override
42
+ 2. `~/.config/structured-context/config.json` (or `$XDG_CONFIG_HOME/structured-context/config.json`)
43
+ 3. `./config.json` in the current working directory
44
+
45
+ See `config.example.json` for the full structure. The config maps space names to paths, with optional Miro integration fields and global defaults. Paths in config files are resolved relative to the config file.
46
+
47
+ **Including spaces from other configs:** Use `includeSpacesFrom` to import space definitions from other config files. This is useful for aggregating spaces from multiple projects into a central config, reducing the need to specify `--config` on CLI commands. Duplicate space names are not allowed.
48
+
49
+ **Plugins and markdown plugin config:** See `sctx docs config` for the full reference including `fieldMap`, `typeInference`, `templateDir`, filter views, and plugin loading rules.
50
+
51
+ ### Spaces
52
+
53
+ A space is a named directory or single file registered in the config. Spaces let you reference content by name instead of path:
54
+
55
+ ```bash
56
+ sctx validate ProductX
57
+ ```
58
+
59
+ ### Schemas
60
+
61
+ Schemas define the structure and rules for the entities in a space, allowing customisation and extension to different models.
62
+
63
+ Two schemas (`general` and `strict_ost`) are included. The general 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.
64
+
65
+ sctx schemas use a metaschema based on JSON Schema Draft-07 that adds a top-level `$metadata` block:
66
+
67
+ ```json5
68
+ "$metadata": {
69
+ "hierarchy": {
70
+ "levels": ["outcome", { "type": "opportunity", "selfRef": true }, "solution", "assumption_test"],
71
+ "allowSkipLevels": false
72
+ },
73
+ "relationships": [
74
+ {
75
+ "parent": "opportunity",
76
+ "type": "assumption",
77
+ "templateFormat": "table",
78
+ "matchers": ["Assumptions"]
79
+ }
80
+ ],
81
+ "aliases": { "experiment": "assumption_test" },
82
+ "rules": [
83
+ {
84
+ "id": "active-outcome-count",
85
+ "category": "workflow",
86
+ "description": "Only one outcome should be active at a time",
87
+ "scope": "global",
88
+ "check": "$count(nodes[resolvedType='outcome' and status='active']) <= 1"
89
+ }
90
+ ]
91
+ }
92
+ ```
93
+
94
+ Rules are a flat array (`rules[]`) with per-rule `category`.
95
+
96
+ Schema hierarchy levels support DAG (multi-parent) relationships via configurable edge fields. Each entry in `$metadata.hierarchy.levels` can be a plain type name string (defaults to `parent` field on child nodes) or an object:
97
+
98
+ ```json5
99
+ // Example fragments for hierarchy level objects:
100
+ { "type": "opportunity", "selfRef": true }
101
+ { "type": "solution", "field": "fulfills", "multiple": true }
102
+ { "type": "requirement", "field": "generates", "fieldOn": "parent", "multiple": true }
103
+ { "type": "solution", "field": "solutions", "fieldOn": "parent", "multiple": true, "selfRefField": "parent" }
104
+ ```
105
+
106
+ | Property | Default | Description |
107
+ |---|---|---|
108
+ | `type` | required | The node type at this hierarchy level |
109
+ | `field` | `"parent"` | Name of the edge field |
110
+ | `fieldOn` | `"child"` | Which side holds the field: `"child"` (child points up) or `"parent"` (parent points down) |
111
+ | `multiple` | `false` | Whether the field is an array of wikilinks (enables multi-parent DAG) |
112
+ | `selfRef` | `false` | Whether a node of this type may reference a same-type parent |
113
+ | `selfRefField` | _undefined_ | Optional field for same-type parent relationships (always on child-side and singular) |
114
+ | `templateFormat` | _undefined_ | Embedding hint (`"list"`, `"table"`, `"heading"`). When set alongside `matchers`, enables hierarchy embedding in typed pages |
115
+ | `matchers` | _undefined_ | Heading patterns (strings or `/regex/`) to match for hierarchy embedding. Case-insensitive. |
116
+ | `embeddedTemplateFields` | _undefined_ | Column names for table stubs when `template-sync` generates templates |
117
+
118
+ The `selfRefField` property enables different fields for regular vs same-type relationships. For example, requirements can list solutions via `solutions` on the requirement node, while solutions can reference parent solutions via `parent` on the solution node.
119
+
120
+ **Hierarchy embedding** — when `templateFormat` and `matchers` are set on a level, typed pages may include section headings that signal embedded content for that type without explicit `[type:: x]` annotations. Two patterns:
121
+
122
+ - **Child-level**: heading matches the child level's type/matchers → list or table items create child nodes.
123
+ - **Parent-level references**: heading matches the *parent* level's type/matchers → bare wikilink items (`- [[X]]`) populate the current node's reference field rather than creating new nodes. Useful for listing parent relationships inline.
124
+
125
+ Bare wikilink items (`- [[Existing Node]]`) in any embedding section populate a field rather than creating a new node.
126
+
127
+ **Adjacent Relationships** (`$metadata.relationships`) define connections between types outside the primary hierarchy — such as an `activity` having many `task` nodes. They drive embedded parsing (typed headings, lists, tables) and template generation.
128
+
129
+ | Property | Default | Description |
130
+ |---|---|---|
131
+ | `parent` | required | Parent canonical type |
132
+ | `type` | required | Child canonical type |
133
+ | `field` | `"parent"` | Frontmatter field holding the wikilink(s). Required when `fieldOn: "parent"`. |
134
+ | `fieldOn` | `"child"` | `"child"`: child holds a link pointing up. `"parent"`: parent holds an array of child links. |
135
+ | `templateFormat` | `"page"` | Hint for `template-sync`: `"table"`, `"list"`, or `"heading"` |
136
+ | `matchers` | `[]` | Heading text to match for embedded parsing (strings or `/regex/`). Case-insensitive. |
137
+ | `multiple` | `true` | Whether multiple children are expected |
138
+ | `embeddedTemplateFields` | `[]` | Field names to include as table columns in templates |
139
+
140
+ 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]]"]`).
141
+
142
+ Metadata is composable across `$ref` graphs:
143
+ - zero or one metadata provider may define `hierarchy`
144
+ - `aliases` are shallow-merged (later wins)
145
+ - `rules` merge by `id`; conflicts error unless the later rule sets `override: true`
146
+ - `$metadata.rules` supports `$ref` imports for reusable rule packs
147
+
148
+ If no provider defines `hierarchy`, hierarchy-specific checks are skipped. Reading a `space_on_a_page` file still requires `hierarchy.levels`.
149
+
150
+ **Customizing Schemas:**
151
+ - **Partial schemas**: Files starting with an underscore (like `_sctx_base.json`) are loaded and used to resolve references (using `$ref`).
152
+ - **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.
153
+ - **Loading priority**: Partial schemas are loaded from both the default schema directory and the directory of your specified target schema.
154
+ - **Transitive resolution**: `$ref` chains are resolved recursively across files/schemas (including nested `allOf` usage in partials).
155
+ - **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.
156
+
157
+ Schema resolution order: space config `schema` > global config `schema` > bundled `schemas/general.json`
158
+
159
+ **⚠️ Security Notice: Only use schemas and configuration files from trusted sources.**
160
+
161
+ The tool executes JSONata expressions defined in schema files for rule validation. A maliciously crafted schema could make JSONata access JavaScript's prototype chain and execute arbitrary code. Only use schemas you've created or reviewed personally.
162
+
163
+ ## Usage
164
+
165
+ ### Validate nodes
166
+
167
+ ```bash
168
+ sctx validate <space> [--watch]
169
+ ```
170
+
171
+ Validates markdown files against the JSON schema:
172
+ - Extracts YAML frontmatter from each `.md` file
173
+ - Skips files without frontmatter or without a `type` field
174
+ - Reports validation results with counts and per-file errors
175
+
176
+ ### Show space tree
177
+
178
+ ```bash
179
+ sctx show <space> [--filter <view-or-expression>]
180
+ ```
181
+
182
+ 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.
183
+
184
+ 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.
185
+
186
+ **Filtering:** The `--filter` flag accepts either a named view from the space config, or an inline filter expression. Only nodes matching the expression are shown.
187
+
188
+ ```bash
189
+ # Inline expression
190
+ sctx show <space> --filter "WHERE resolvedType='solution' and status='active'"
191
+
192
+ # Named view from config
193
+ sctx show <space> --filter active-solutions
194
+ ```
195
+
196
+ See [Filter expressions](#filter-expressions) below for expression syntax.
197
+
198
+ ### Filter expressions
199
+
200
+ Filter expressions are used with `--filter` and in config `views`. They use a `SELECT ... WHERE ...` pseudo-DSL:
201
+
202
+ | Form | Meaning |
203
+ |------|---------|
204
+ | `WHERE {jsonata}` | Return nodes where the JSONata predicate is truthy |
205
+ | `SELECT {spec} WHERE {jsonata}` | Filter by WHERE, then expand result via SELECT |
206
+ | `SELECT {spec}` | Expand from all nodes via SELECT (no WHERE filter — returns all nodes, expanded per spec) |
207
+ | `{jsonata}` | Bare JSONata, treated as a WHERE predicate (convenience shorthand) |
208
+
209
+ The WHERE predicate is a [JSONata](https://docs.jsonata.org/overview) expression evaluated per node. Within the expression, each node's fields are accessible directly (e.g. `resolvedType`, `status`, any schema fields like `title`). Two built-in fields are always available regardless of schema: `label` (relative file path, e.g. `"solutions/My Solution.md"`) and `title` (node display name). Additionally, two pre-computed traversal arrays are available:
210
+
211
+ - **`ancestors[]`** — flat array of ancestor nodes, nearest first, deduplicated. Each entry includes all schema fields of the ancestor node, plus:
212
+ - `_field` — the edge field name that connects to the ancestor
213
+ - `_source` — `'hierarchy'` or `'relationship'`
214
+ - `_selfRef` — whether the edge is a same-type (self-referential) link
215
+ - **`descendants[]`** — same structure, for descendant nodes
216
+
217
+ **SELECT spec** expands the result set by walking the graph from matched nodes. The spec is a comma-separated list of directives:
218
+
219
+ | Directive | Meaning |
220
+ |-----------|---------|
221
+ | `ancestors` | All ancestor nodes |
222
+ | `ancestors(type)` | Ancestors of the given resolved type |
223
+ | `descendants` | All descendant nodes |
224
+ | `descendants(type)` | Descendants of the given resolved type |
225
+ | `siblings` | Nodes sharing at least one parent with matched nodes |
226
+ | `relationships` | All nodes connected via a relationship (non-hierarchy) edge |
227
+ | `relationships(childType)` | Relationship-connected nodes of the given child type |
228
+ | `relationships(parentType:childType)` | As above, also filtering by parent type |
229
+ | `relationships(parentType:field:childType)` | Fully qualified: also filtering by edge field name |
230
+
231
+ Multiple directives may be combined: `SELECT ancestors(goal), siblings WHERE ...`
232
+
233
+ **Examples:**
234
+
235
+ ```jsonata
236
+ // All solutions
237
+ WHERE resolvedType='solution'
238
+
239
+ // Active solutions only
240
+ WHERE resolvedType='solution' and status='active'
241
+
242
+ // Solutions whose nearest opportunity ancestor is active
243
+ WHERE resolvedType='solution' and $exists(ancestors[resolvedType='opportunity' and status='active'])
244
+
245
+ // Nodes that have any ancestor goal
246
+ WHERE $exists(ancestors[resolvedType='goal'])
247
+
248
+ // Bare JSONata shorthand (no WHERE keyword)
249
+ resolvedType='solution' and status='active'
250
+
251
+ // Solutions + their opportunity ancestors
252
+ SELECT ancestors(opportunity) WHERE resolvedType='solution'
253
+
254
+ // Solutions + their siblings (other solutions under same opportunity)
255
+ SELECT siblings WHERE resolvedType='solution' and status='active'
256
+
257
+ // Opportunities + their related assumptions
258
+ SELECT relationships(assumption) WHERE resolvedType='opportunity'
259
+ ```
260
+
261
+ ### Generate Mermaid diagram
262
+
263
+ ```bash
264
+ sctx diagram <space> [--output path/to/output.mmd]
265
+ ```
266
+
267
+ Generates a Mermaid `graph TD` diagram from validated space nodes:
268
+ - Uses parent→child relationships from wikilinks
269
+ - Applies type-based styling (different colours per node type and status)
270
+ - Handles orphan nodes (no parent) as a separate cluster
271
+ - Outputs to file or stdout
272
+
273
+ ### Show schema ERD
274
+
275
+ ```bash
276
+ sctx schemas show <schema-file> [--mermaid-erd] [--space <name>]
277
+ ```
278
+
279
+ Generates a Mermaid Entity Relationship Diagram from a schema:
280
+ - Shows all entity types and their properties
281
+ - Displays parent-child relationships based on hierarchy metadata
282
+ - Useful for visualizing schema structure during development
283
+
284
+ Example:
285
+ ```bash
286
+ sctx schemas show general --mermaid-erd
287
+ ```
288
+
289
+ ### Sync space to Miro
290
+
291
+ ```bash
292
+ sctx miro-sync <space> [--new-frame <title>] [--dry-run] [--verbose]
293
+ ```
294
+
295
+ 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.
296
+
297
+ - `--new-frame <title>` — create a new frame on the board and sync into it; auto-saves the resulting `miroFrameId` back to the config file
298
+ - `--dry-run` — show what would change without touching Miro
299
+ - `--verbose` / `-v` — detailed per-card and per-connector output
300
+
301
+ 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.
302
+
303
+ 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.
304
+
305
+ ### Sync templates with schema
306
+
307
+ ```bash
308
+ sctx template-sync <space> [--create-missing] [--dry-run]
309
+ ```
310
+
311
+ Keeps Obsidian template files in sync with schema examples:
312
+ - Matches markdown files in the template directory (defined in config) by `type` field
313
+ - Rewrites frontmatter using description fields and property `examples`
314
+ - `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`.
315
+ - `--dry-run` previews changes without writing files
316
+
317
+ ## Development
318
+
319
+ ```bash
320
+ # Run a command against a configured space
321
+ bun run src/index.ts validate personal
322
+
323
+ # Run type checking (checks all code including tests)
324
+ bun run typecheck
325
+
326
+ # Run core unit tests
327
+ bun run test
328
+
329
+ # Run occasional smoke tests against all locally configured spaces
330
+ bun run test:smoke
331
+
332
+ # Build compiled output
333
+ bun run build
334
+
335
+ # Link built package locally so `bunx structured-context` picks up changes
336
+ bun link
337
+ ```
338
+
339
+ ### Releasing
340
+ ```bash
341
+ npm login # authenticate with npm registry if needed
342
+ bun pm version patch # or minor / major — runs lint, tests, then pushes with tags
343
+ npm publish
344
+ ```
345
+
346
+ ## License
347
+
348
+ MIT
@@ -0,0 +1,5 @@
1
+ import type { SpaceContext } from '../types';
2
+ export declare function diagram(context: SpaceContext, options: {
3
+ output?: string;
4
+ filter?: string;
5
+ }): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { executeRender } from '../render/render';
3
+ export async function diagram(context, options) {
4
+ const result = await executeRender('markdown.mermaid', context, { filter: options.filter });
5
+ if (options.output) {
6
+ writeFileSync(options.output, result);
7
+ console.log(`Mermaid diagram written to ${options.output}`);
8
+ }
9
+ else {
10
+ console.log(result);
11
+ }
12
+ }
@@ -0,0 +1 @@
1
+ export declare function docs(topic?: string): void;
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const TOPICS = {
4
+ concepts: 'concepts.md',
5
+ config: 'config.md',
6
+ schema: 'schemas.md',
7
+ rules: 'rules.md',
8
+ };
9
+ export function docs(topic) {
10
+ let filePath;
11
+ if (!topic) {
12
+ filePath = join(import.meta.dir, '..', '..', 'README.md');
13
+ }
14
+ else {
15
+ const file = TOPICS[topic];
16
+ if (!file) {
17
+ const available = Object.keys(TOPICS).join(', ');
18
+ console.error(`Unknown topic "${topic}". Available: ${available}`);
19
+ process.exit(1);
20
+ }
21
+ filePath = join(import.meta.dir, '..', '..', 'docs', file);
22
+ }
23
+ const content = readFileSync(filePath, 'utf-8');
24
+ const cols = process.stdout.columns ?? 80;
25
+ const rendered = Bun.markdown.render(content, {
26
+ heading: (children, { level }) => {
27
+ const prefix = '#'.repeat(level);
28
+ if (level === 1)
29
+ return `\x1b[1;4m${prefix} ${children}\x1b[0m\n\n`;
30
+ if (level === 2)
31
+ return `\n\x1b[1m${prefix} ${children}\x1b[0m\n${'─'.repeat(Math.min(children.length + level + 1, cols))}\n`;
32
+ return `\n\x1b[1m${prefix} ${children}\x1b[0m\n`;
33
+ },
34
+ paragraph: (children) => `${children}\n\n`,
35
+ strong: (children) => `\x1b[1m**${children}**\x1b[22m`,
36
+ emphasis: (children) => `\x1b[3m*${children}*\x1b[23m`,
37
+ codespan: (children) => `\x1b[96m\`${children}\`\x1b[39m`,
38
+ code: (children, meta) => {
39
+ const lang = meta?.language ?? '';
40
+ return `\x1b[96m\`\`\`${lang}\n${children}\`\`\`\x1b[0m\n`;
41
+ },
42
+ blockquote: (children) => `${children
43
+ .split('\n')
44
+ .map((l) => `\x1b[2m> ${l}\x1b[0m`)
45
+ .join('\n')}\n`,
46
+ table: (children) => `${children}\n`,
47
+ thead: (children) => {
48
+ const colCount = (children.split('\n')[0] ?? '').split('|').length - 2;
49
+ const sep = `\x1b[2m| ${Array(colCount).fill('---').join(' | ')} |\x1b[0m\n`;
50
+ return `${children}${sep}`;
51
+ },
52
+ tbody: (children) => children,
53
+ tr: (children) => `${children}|\n`,
54
+ th: (children) => `| \x1b[1m${children}\x1b[22m `,
55
+ td: (children) => `| ${children} `,
56
+ list: (children) => `${children}\n`,
57
+ listItem: (children, meta) => {
58
+ const { depth = 0, ordered = false, index = 0, } = meta;
59
+ const indent = ' '.repeat(depth);
60
+ const bullet = ordered ? `${index + 1}.` : '-';
61
+ return `${indent}${bullet} ${children.trimEnd()}\n`;
62
+ },
63
+ hr: () => `\x1b[2m---\x1b[0m\n`,
64
+ link: (children, { href }) => children === href ? `\x1b[4;34m${href}\x1b[0m` : `[${children}](\x1b[4;34m${href}\x1b[0m)`,
65
+ });
66
+ process.stdout.write(rendered);
67
+ }
@@ -0,0 +1,2 @@
1
+ import type { SpaceContext } from '../types';
2
+ export declare function dump(context: SpaceContext): Promise<void>;
@@ -0,0 +1,6 @@
1
+ import { JSON5 } from 'bun';
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));
6
+ }
@@ -0,0 +1 @@
1
+ export declare function listPlugins(): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { builtinPlugins } from '../plugins';
2
+ import { discoverPlugins } from '../plugins/loader';
3
+ function showConfigSchema(plugin) {
4
+ console.log(JSON.stringify(plugin.configSchema, null, 2));
5
+ }
6
+ export async function listPlugins() {
7
+ const builtinNames = new Set(builtinPlugins.map((p) => p.name));
8
+ const plugins = await discoverPlugins();
9
+ const builtins = plugins.filter((p) => builtinNames.has(p.name));
10
+ const external = plugins.filter((p) => !builtinNames.has(p.name));
11
+ console.log('Built-in plugins:');
12
+ for (const plugin of builtins) {
13
+ console.log(` ${plugin.name}`);
14
+ showConfigSchema(plugin);
15
+ }
16
+ if (external.length > 0) {
17
+ console.log('\nConfig-adjacent plugins:');
18
+ for (const plugin of external) {
19
+ console.log(` ${plugin.name}`);
20
+ showConfigSchema(plugin);
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,6 @@
1
+ import type { SpaceContext } from '../types';
2
+ export declare function render(context: SpaceContext, format: string, options: {
3
+ filter?: string;
4
+ output?: string;
5
+ }): Promise<void>;
6
+ export declare function renderList(context?: SpaceContext): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { discoverPlugins, loadPlugins } from '../plugins/loader';
3
+ import { buildFormatRegistry } from '../render/registry';
4
+ import { executeRender } from '../render/render';
5
+ 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}`);
10
+ }
11
+ else {
12
+ process.stdout.write(result);
13
+ if (!result.endsWith('\n'))
14
+ process.stdout.write('\n');
15
+ }
16
+ }
17
+ export async function renderList(context) {
18
+ let loaded;
19
+ if (context) {
20
+ const pluginMap = context.space?.plugins ?? {};
21
+ loaded = await loadPlugins(pluginMap, context.configDir);
22
+ }
23
+ else {
24
+ const discovered = await discoverPlugins();
25
+ loaded = discovered.map((plugin) => ({ plugin, pluginConfig: {} }));
26
+ }
27
+ const registry = buildFormatRegistry(loaded);
28
+ if (registry.length === 0) {
29
+ console.log('No render formats available.');
30
+ return;
31
+ }
32
+ for (const entry of registry) {
33
+ console.log(` ${entry.qualifiedName.padEnd(24)} ${entry.format.description}`);
34
+ }
35
+ }
@@ -0,0 +1,6 @@
1
+ export declare function listSchemas(): void;
2
+ export declare function showSchema(file: string | undefined, options: {
3
+ space?: string;
4
+ raw: boolean;
5
+ mermaidErd?: boolean;
6
+ }): void;