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.
- package/README.md +348 -0
- package/dist/commands/diagram.d.ts +5 -0
- package/dist/commands/diagram.js +12 -0
- package/dist/commands/docs.d.ts +1 -0
- package/dist/commands/docs.js +67 -0
- package/dist/commands/dump.d.ts +2 -0
- package/dist/commands/dump.js +6 -0
- package/dist/commands/plugins.d.ts +1 -0
- package/dist/commands/plugins.js +23 -0
- package/dist/commands/render.d.ts +6 -0
- package/dist/commands/render.js +35 -0
- package/dist/commands/schemas.d.ts +6 -0
- package/dist/commands/schemas.js +268 -0
- package/dist/commands/show.d.ts +4 -0
- package/dist/commands/show.js +7 -0
- package/dist/commands/spaces.d.ts +1 -0
- package/dist/commands/spaces.js +36 -0
- package/dist/commands/template-sync.d.ts +3 -0
- package/dist/commands/template-sync.js +13 -0
- package/dist/commands/validate-file.d.ts +28 -0
- package/dist/commands/validate-file.js +133 -0
- package/dist/commands/validate.d.ts +16 -0
- package/dist/commands/validate.js +349 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.js +179 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +6 -0
- package/dist/filter/augment-nodes.d.ts +23 -0
- package/dist/filter/augment-nodes.js +95 -0
- package/dist/filter/expand-include.d.ts +62 -0
- package/dist/filter/expand-include.js +181 -0
- package/dist/filter/filter-nodes.d.ts +21 -0
- package/dist/filter/filter-nodes.js +73 -0
- package/dist/filter/parse-expression.d.ts +20 -0
- package/dist/filter/parse-expression.js +60 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +161 -0
- package/dist/integrations/miro/cache.d.ts +21 -0
- package/dist/integrations/miro/cache.js +55 -0
- package/dist/integrations/miro/client.d.ts +99 -0
- package/dist/integrations/miro/client.js +118 -0
- package/dist/integrations/miro/layout.d.ts +28 -0
- package/dist/integrations/miro/layout.js +72 -0
- package/dist/integrations/miro/styles.d.ts +11 -0
- package/dist/integrations/miro/styles.js +65 -0
- package/dist/integrations/miro/sync.d.ts +8 -0
- package/dist/integrations/miro/sync.js +347 -0
- package/dist/plugin-api.d.ts +12 -0
- package/dist/plugin-api.js +7 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/loader.d.ts +21 -0
- package/dist/plugins/loader.js +104 -0
- package/dist/plugins/markdown/index.d.ts +48 -0
- package/dist/plugins/markdown/index.js +51 -0
- package/dist/plugins/markdown/parse-embedded.d.ts +90 -0
- package/dist/plugins/markdown/parse-embedded.js +663 -0
- package/dist/plugins/markdown/read-space.d.ts +7 -0
- package/dist/plugins/markdown/read-space.js +89 -0
- package/dist/plugins/markdown/render-bullets.d.ts +2 -0
- package/dist/plugins/markdown/render-bullets.js +42 -0
- package/dist/plugins/markdown/render-mermaid.d.ts +2 -0
- package/dist/plugins/markdown/render-mermaid.js +57 -0
- package/dist/plugins/markdown/template-sync.d.ts +16 -0
- package/dist/plugins/markdown/template-sync.js +294 -0
- package/dist/plugins/markdown/util.d.ts +19 -0
- package/dist/plugins/markdown/util.js +80 -0
- package/dist/plugins/util.d.ts +60 -0
- package/dist/plugins/util.js +7 -0
- package/dist/read/read-space.d.ts +2 -0
- package/dist/read/read-space.js +22 -0
- package/dist/read/resolve-graph-edges.d.ts +11 -0
- package/dist/read/resolve-graph-edges.js +201 -0
- package/dist/read/wikilink-utils.d.ts +16 -0
- package/dist/read/wikilink-utils.js +38 -0
- package/dist/render/registry.d.ts +13 -0
- package/dist/render/registry.js +22 -0
- package/dist/render/render.d.ts +4 -0
- package/dist/render/render.js +28 -0
- package/dist/schema/evaluate-rule.d.ts +30 -0
- package/dist/schema/evaluate-rule.js +82 -0
- package/dist/schema/metadata-contract.d.ts +538 -0
- package/dist/schema/metadata-contract.js +115 -0
- package/dist/schema/schema-refs.d.ts +22 -0
- package/dist/schema/schema-refs.js +168 -0
- package/dist/schema/schema.d.ts +27 -0
- package/dist/schema/schema.js +378 -0
- package/dist/schema/validate-graph.d.ts +24 -0
- package/dist/schema/validate-graph.js +141 -0
- package/dist/schema/validate-rules.d.ts +10 -0
- package/dist/schema/validate-rules.js +51 -0
- package/dist/schemas/_ost_strict.json +81 -0
- package/dist/schemas/_sctx_base.json +72 -0
- package/dist/schemas/general.json +261 -0
- package/dist/schemas/generated/_structured_context_schema_meta.json +191 -0
- package/dist/schemas/knowledge_wiki.json +206 -0
- package/dist/schemas/strict_ost.json +97 -0
- package/dist/space-graph.d.ts +28 -0
- package/dist/space-graph.js +82 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.js +0 -0
- package/docs/concepts.md +391 -0
- package/docs/config.md +140 -0
- package/docs/rules.md +120 -0
- package/docs/schemas.md +340 -0
- package/package.json +69 -0
- package/schemas/_ost_strict.json +81 -0
- package/schemas/_sctx_base.json +72 -0
- package/schemas/general.json +261 -0
- package/schemas/generated/_structured_context_schema_meta.json +191 -0
- package/schemas/knowledge_wiki.json +206 -0
- package/schemas/strict_ost.json +97 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { AnySchemaObject, SchemaObject, ValidateFunction } from 'ajv';
|
|
2
|
+
import type { Config, SpaceConfig } from './config';
|
|
3
|
+
import type { MetadataContractHierarchyLevel, MetadataContractRelationship, Rule, SharedEdgeFields } from './schema/metadata-contract';
|
|
4
|
+
/**
|
|
5
|
+
* Minimal normalized edge for graph resolution.
|
|
6
|
+
* Derives routing fields from SharedEdgeFields to stay in sync with the schema.
|
|
7
|
+
*/
|
|
8
|
+
export interface EdgeDefinition extends Required<Pick<SharedEdgeFields, 'field' | 'fieldOn' | 'multiple'>> {
|
|
9
|
+
type: string;
|
|
10
|
+
parent: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Normalized hierarchy level — all edge fields are required after schema.ts normalization.
|
|
14
|
+
* The raw schema input type is MetadataContractHierarchyLevel (optional fields).
|
|
15
|
+
*/
|
|
16
|
+
export type HierarchyLevel = Omit<MetadataContractHierarchyLevel, 'field' | 'fieldOn' | 'multiple' | 'selfRef'> & {
|
|
17
|
+
field: string;
|
|
18
|
+
fieldOn: 'child' | 'parent';
|
|
19
|
+
multiple: boolean;
|
|
20
|
+
selfRef: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Normalized relationship — edge routing fields are required after schema.ts normalization.
|
|
24
|
+
* The raw schema input type is MetadataContractRelationship (optional edge fields).
|
|
25
|
+
*/
|
|
26
|
+
export type Relationship = Omit<MetadataContractRelationship, 'field' | 'fieldOn' | 'multiple'> & {
|
|
27
|
+
field: string;
|
|
28
|
+
fieldOn: 'child' | 'parent';
|
|
29
|
+
multiple: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A resolved parent reference, capturing not just the parent title but the edge context
|
|
33
|
+
* from which it was resolved. All hierarchy levels and relationships produce entries of
|
|
34
|
+
* this type, forming a single labelled graph rather than separate structures.
|
|
35
|
+
*/
|
|
36
|
+
export type ResolvedParentRef = {
|
|
37
|
+
/** Canonical title of the parent node. */
|
|
38
|
+
title: string;
|
|
39
|
+
/** The field name that contained the wikilink (e.g. 'parent', 'key_activities', 'produces_data'). */
|
|
40
|
+
field: string;
|
|
41
|
+
/** Whether this edge originates from a hierarchy level or a relationship definition. */
|
|
42
|
+
source: 'hierarchy' | 'relationship';
|
|
43
|
+
/** Whether the parent and child are the same node type (self-referential edge). */
|
|
44
|
+
selfRef: boolean;
|
|
45
|
+
/** Whether the edge field is on the child node (default) or on the parent node (fieldOn:'parent' edges). */
|
|
46
|
+
fieldOn: 'child' | 'parent';
|
|
47
|
+
};
|
|
48
|
+
export type UnresolvedRef = {
|
|
49
|
+
/** Source identifier of the node containing the broken link. */
|
|
50
|
+
label: string;
|
|
51
|
+
/** Raw wikilink value (or String(rawField) for invalid_shape). */
|
|
52
|
+
ref: string;
|
|
53
|
+
/** Field name that contained the link. */
|
|
54
|
+
field: string;
|
|
55
|
+
reason: 'not_found' | 'ambiguous' | 'invalid_shape';
|
|
56
|
+
/** Human-readable message matching validate-graph output format. */
|
|
57
|
+
message: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* A node as produced by a parse plugin — raw type from content, no graph resolution applied.
|
|
61
|
+
* Core enriches this into a SpaceNode after parsing.
|
|
62
|
+
*/
|
|
63
|
+
export type BaseNode = {
|
|
64
|
+
/** Source identifier for error messages (filename or heading title) */
|
|
65
|
+
label: string;
|
|
66
|
+
/** Canonical title of the node (from schemaData.title). First-class accessor. */
|
|
67
|
+
title: string;
|
|
68
|
+
/** Fields validated against the active schema. */
|
|
69
|
+
schemaData: Record<string, unknown>;
|
|
70
|
+
/** Valid navigation targets this node can be linked to (wikilink key without [[ ]]). */
|
|
71
|
+
linkTargets: string[];
|
|
72
|
+
/** Raw type string from content, as written by the user. */
|
|
73
|
+
type: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A fully resolved node — enriched by core after plugin parsing.
|
|
77
|
+
* Adds canonical type (after alias resolution) and resolved parent graph edges.
|
|
78
|
+
*/
|
|
79
|
+
export type SpaceNode = BaseNode & {
|
|
80
|
+
/** Resolved canonical type (after applying type aliases from schema metadata). */
|
|
81
|
+
resolvedType: string;
|
|
82
|
+
/**
|
|
83
|
+
* Resolved parent references derived from all edge fields (hierarchy levels + relationships).
|
|
84
|
+
* Each entry carries the parent title, the field it came from, and its edge context.
|
|
85
|
+
* Always present, empty if no parents resolved.
|
|
86
|
+
*/
|
|
87
|
+
resolvedParents: ResolvedParentRef[];
|
|
88
|
+
};
|
|
89
|
+
/** Rule categories for organizing executable validation rules */
|
|
90
|
+
export type RuleCategory = 'validation' | 'coherence' | 'workflow' | 'best-practice';
|
|
91
|
+
export type { Rule } from './schema/metadata-contract';
|
|
92
|
+
export type RuleViolation = {
|
|
93
|
+
file: string;
|
|
94
|
+
ruleId: string;
|
|
95
|
+
category: RuleCategory;
|
|
96
|
+
description: string;
|
|
97
|
+
};
|
|
98
|
+
export type GraphViolation = {
|
|
99
|
+
file: string;
|
|
100
|
+
nodeType: string;
|
|
101
|
+
nodeTitle: string;
|
|
102
|
+
parentType: string;
|
|
103
|
+
parentTitle: string;
|
|
104
|
+
description: string;
|
|
105
|
+
};
|
|
106
|
+
export type SchemaMetadata = {
|
|
107
|
+
hierarchy?: {
|
|
108
|
+
levels: HierarchyLevel[];
|
|
109
|
+
allowSkipLevels?: boolean;
|
|
110
|
+
};
|
|
111
|
+
typeAliases?: Record<string, string>;
|
|
112
|
+
rules?: Rule[];
|
|
113
|
+
relationships?: Relationship[];
|
|
114
|
+
};
|
|
115
|
+
export type SchemaWithMetadata = SchemaObject & {
|
|
116
|
+
metadata: SchemaMetadata;
|
|
117
|
+
};
|
|
118
|
+
export type ReadSpaceResult = {
|
|
119
|
+
/** Fully resolved nodes produced by the plugin and enriched by core. */
|
|
120
|
+
nodes: SpaceNode[];
|
|
121
|
+
/** Paths/items the plugin skipped during parsing, for any reason. */
|
|
122
|
+
parseIgnored: string[];
|
|
123
|
+
/** Plugin diagnostics: keyed scalar or list values. */
|
|
124
|
+
diagnostics: Record<string, number | string | string[]>;
|
|
125
|
+
/** Name of the plugin that produced the nodes. */
|
|
126
|
+
source: string;
|
|
127
|
+
/** Broken/invalid wikilink refs collected during graph edge resolution. */
|
|
128
|
+
unresolvedRefs: UnresolvedRef[];
|
|
129
|
+
};
|
|
130
|
+
export type SpaceContext = {
|
|
131
|
+
/** Matching space config entry. */
|
|
132
|
+
space: SpaceConfig;
|
|
133
|
+
/** Full loaded config. */
|
|
134
|
+
config: Config;
|
|
135
|
+
/** Absolute path to the resolved schema. */
|
|
136
|
+
resolvedSchemaPath: string;
|
|
137
|
+
/** Full loaded schema with metadata embedded. */
|
|
138
|
+
schema: SchemaWithMetadata;
|
|
139
|
+
/** Registry for resolving $ref in schema (maps $ref IDs to schema objects). */
|
|
140
|
+
schemaRefRegistry: Map<string, AnySchemaObject>;
|
|
141
|
+
/** Compiled AJV validator for the schema. */
|
|
142
|
+
schemaValidator: ValidateFunction;
|
|
143
|
+
/** Directory of the config file that defines this space. Used for resolving relative paths in plugin configs. */
|
|
144
|
+
configDir: string;
|
|
145
|
+
};
|
package/dist/types.js
ADDED
|
File without changes
|
package/docs/concepts.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Structured Context: Concepts and Terminology
|
|
2
|
+
|
|
3
|
+
This document is the canonical reference for concepts and terminology used in this project. It focuses on the meta-concepts the project supports, not the content of specific frameworks modelled in schemas. Before naming things in code, tests, comments, or documentation, check definitions here for consistency, and update them here when the project's "world view" changes, avoiding blurry terms as much as possible.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Space
|
|
8
|
+
|
|
9
|
+
A **space** is a named collection of nodes organised according to a schema. Spaces are the primary unit of organisation — a space has a backing format (a `space directory` or a `space on a page` file) and may be registered in `config.json` with a name for convenient access.
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TD
|
|
13
|
+
subgraph dir [Space Directory]
|
|
14
|
+
direction TB
|
|
15
|
+
tp[Typed Page<br>type: goal in frontmatter]
|
|
16
|
+
en[Embedded Nodes<br>headings, bullets, or table rows<br>explicitly typed, relationship-implied,<br>or anchor-implied]
|
|
17
|
+
other[Other files<br>no frontmatter → skipped<br>no type field → nonSpace]
|
|
18
|
+
tp -->|body parsed for| en
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph soap [Space on a Page]
|
|
22
|
+
direction TB
|
|
23
|
+
sf[Single .md file<br>type: space_on_a_page]
|
|
24
|
+
hn[Heading nodes<br>depth → type via hierarchy<br>or relationship-implied]
|
|
25
|
+
bn[Bullet nodes<br>explicitly typed or relationship-implied]
|
|
26
|
+
tn[Table rows<br>typed via relationship heading<br>or first-column name]
|
|
27
|
+
sf -->|headings| hn
|
|
28
|
+
sf -->|typed bullets| bn
|
|
29
|
+
sf -->|typed tables| tn
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
pe[parse-embedded<br>extractEmbeddedNodes]
|
|
33
|
+
nodes[(Space Nodes)]
|
|
34
|
+
|
|
35
|
+
dir --> pe
|
|
36
|
+
soap --> pe
|
|
37
|
+
pe --> nodes
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> The term "space" is preferred over "OST" or "tree" because the tooling is not limited to a specific framework, and future schemas may not be strictly tree-shaped.
|
|
41
|
+
|
|
42
|
+
### Space directory
|
|
43
|
+
|
|
44
|
+
A **space directory** is a directory of markdown files that backs a `space`. Each file may represent a `space node`, embed child nodes in its body, or be an unrelated file that the tooling ignores.
|
|
45
|
+
|
|
46
|
+
Each `.md` file with a `type` frontmatter field is a **typed page** — it represents one node. Its body is also scanned for **embedded nodes**:
|
|
47
|
+
|
|
48
|
+
- **Heading with `[type:: x]`** or **anchor-implied type** (e.g. `## My Goal ^goal1`) → becomes a child node.
|
|
49
|
+
- **Relationship headings** (e.g. `### Assumptions`) → when matched to a relationship definition in the schema, signals that following content (list items or table rows) should be typed as that relationship's child type, without requiring explicit inline annotations.
|
|
50
|
+
- **Untyped headings** → update the depth stack for parent resolution but do not become nodes.
|
|
51
|
+
- **Typed bullet items** (`- [type:: solution] Title`) → become child nodes at any nesting depth.
|
|
52
|
+
- **Table rows** under a relationship heading → become child nodes of the relationship type.
|
|
53
|
+
- **YAML blocks** and **unbracketed `key:: value` paragraph fields** → merged into the current node's `schemaData`.
|
|
54
|
+
|
|
55
|
+
Parsing behaviour for a space directory:
|
|
56
|
+
- Files declaring a `space node` type via frontmatter are included as nodes.
|
|
57
|
+
- Such files may also contain `embedded nodes` in their body, which are extracted and included.
|
|
58
|
+
- Files declaring a `tooling type` (e.g. `space_on_a_page`, `dashboard`) are excluded from the node set.
|
|
59
|
+
- Files without frontmatter, or without a `type` field, are excluded from the node set (unless **type inference** is configured — see below).
|
|
60
|
+
- Non-markdown files are not scanned.
|
|
61
|
+
|
|
62
|
+
#### Type inference
|
|
63
|
+
|
|
64
|
+
When `typeInference` is configured on the markdown plugin, files without an explicit `type` field in frontmatter can have their type inferred from their folder path. Explicit `type:` in frontmatter always takes precedence.
|
|
65
|
+
|
|
66
|
+
Two modes are available:
|
|
67
|
+
|
|
68
|
+
- **`folder-name`** (default) — the leaf directory name is matched case-insensitively against the schema's known type names and alias keys. For example, a file at `concept/page.md` is inferred as type `concept`; a file at `study/page.md` is inferred as `source` if `study` is an alias for `source` in the schema. A folder name that is neither a type name nor an alias key results in no inference.
|
|
69
|
+
|
|
70
|
+
- **`folderMap`** — an explicit map from folder path (relative to space root) to a type name or alias. Replaces auto-matching entirely; only folders listed in the map are inferred. Longest-prefix matching is used when folder paths overlap. An unresolvable value (not a known type or alias) is a hard error at parse time.
|
|
71
|
+
|
|
72
|
+
### Space on a page
|
|
73
|
+
|
|
74
|
+
**Space on a page** is a single-file backing format for a `space`. An entire planning tree is represented in one markdown document, using heading hierarchy, bullet point annotations, and `anchor` syntax. No separate per-node files are used. This format is most useful for the early development stages of a space, keeping information together in one file with less "boilerplate".
|
|
75
|
+
|
|
76
|
+
A file in this format carries `type: space_on_a_page` in its frontmatter. It is not itself a `space node` — it is a container.
|
|
77
|
+
|
|
78
|
+
Key properties:
|
|
79
|
+
- Heading hierarchy determines node depth and infers `space node` type (depth-based type inference).
|
|
80
|
+
- Heading levels must not skip — each level must be exactly one deeper than its parent.
|
|
81
|
+
- Typed bullets work the same as in typed pages.
|
|
82
|
+
- A horizontal rule (`---`) terminates parsing; headings below it are ignored.
|
|
83
|
+
|
|
84
|
+
#### Preamble
|
|
85
|
+
|
|
86
|
+
**Preamble** is content in a `space on a page` document that appears before the first heading. It is parsed but discarded — not associated with any node.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Space node
|
|
91
|
+
|
|
92
|
+
A **space node** (or **node** for short) is a single entity in a `space` — a named, typed item defined in the schema. Nodes are the primary content of a space.
|
|
93
|
+
|
|
94
|
+
Node types are defined by the schema in use and may vary across schemas. Examples from the default schema: `vision`, `mission`, `goal`, `opportunity`, `solution`. The tooling is not prescriptive about which types exist — schemas are designed to be extended and replaced.
|
|
95
|
+
|
|
96
|
+
> `space_on_a_page` and `dashboard` are not `space node` types — they are `tooling types`.
|
|
97
|
+
|
|
98
|
+
### Embedded node
|
|
99
|
+
|
|
100
|
+
An **embedded node** is a `space node` defined *within* a containing document rather than as its own file. Embedded nodes are declared using markdown heading syntax with inline field annotations (e.g. `[type:: goal]`) or `anchor-implied types`, and are extracted at parse time.
|
|
101
|
+
|
|
102
|
+
A `typed page` may contain embedded nodes in its body. Those nodes become full members of the parsed node set, with `parent references` wired to their containing page or enclosing heading.
|
|
103
|
+
|
|
104
|
+
### Type alias
|
|
105
|
+
|
|
106
|
+
A **type alias** is an alternative name accepted in the `type` field for a given `space node` type. Aliases allow teams to use their own vocabulary while still receiving schema validation. For example, a schema might accept `outcome` as an alias for `goal`.
|
|
107
|
+
|
|
108
|
+
A `space node`'s resolved type (`resolvedType`) is its canonical type after alias resolution. Prefer resolvedType over the raw type field for all comparisons in rules and hierarchy checks.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Typed page
|
|
113
|
+
|
|
114
|
+
A **typed page** is a markdown file whose frontmatter declares a `space node` type (e.g. `type: goal`). The file itself represents one node, and its body may additionally contain `embedded nodes`.
|
|
115
|
+
|
|
116
|
+
Typed pages are distinct from `space on a page` files: a typed page *is* a `space node`; a `space_on_a_page` file is merely a container.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Schema
|
|
121
|
+
|
|
122
|
+
A **schema** defines the valid structure for nodes in a `space`: the fields, types, constraints, and descriptive `rules` for each entity type. A space uses the default schema unless a custom one is declared in its config.
|
|
123
|
+
|
|
124
|
+
The schema handles structural validation. Cross-node and workflow checks are handled by executable `rules` defined in `$metadata.rules`.
|
|
125
|
+
|
|
126
|
+
Schemas are composable: structural definitions and metadata can be sourced across `$ref` graphs, then merged deterministically (root metadata applied last, single hierarchy provider, aliases merged, rules merged by `id` with explicit override semantics).
|
|
127
|
+
|
|
128
|
+
### Rules
|
|
129
|
+
|
|
130
|
+
**Rules** are descriptive, and potentially executable, checks applied to nodes beyond what structural schema validation can express. Rules encode qualitative guidance and best practices alongside the schema, making them available to both tooling and agent skills.
|
|
131
|
+
|
|
132
|
+
Rules may be:
|
|
133
|
+
- **Descriptive** — human-readable guidance, useful as documentation and as structured input to agent skills
|
|
134
|
+
- **Executable** — mechanically evaluable expressions (e.g. "no more than one `active` node of a given type at a time")
|
|
135
|
+
- **Quantitative** — numeric thresholds or counts applied to node sets
|
|
136
|
+
- **Stage-based** — triggered only when a node's `status` meets a condition
|
|
137
|
+
- **Qualitative** — checks on content and framing (e.g. ensuring an opportunity is stated in the user's voice, not as a business goal)
|
|
138
|
+
- **Cross-entity** — checks spanning multiple nodes or levels of the tree
|
|
139
|
+
- **Coherence** — verifying that statements across related nodes credibly support one another
|
|
140
|
+
- **Best-practice** — guidance encoded as checks (e.g. flagging solution-framing in problem descriptions)
|
|
141
|
+
|
|
142
|
+
Rules are distinct from schema validation: the schema checks structure; rules check meaning and quality.
|
|
143
|
+
|
|
144
|
+
See [docs/rules.md](rules.md) for the rules reference, including JSONata expression syntax and the full `$metadata` field reference.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Tooling types
|
|
149
|
+
|
|
150
|
+
**Tooling types** are `type` values recognised by the schema and tooling but not treated as `space nodes`. They serve organisational or display purposes:
|
|
151
|
+
|
|
152
|
+
- **`space_on_a_page`** — a container file for a `space on a page`. Not itself a node.
|
|
153
|
+
- **`dashboard`** — a summary view for a `space directory`. Conceptually similar to `space on a page` in that it presents a high-level, single-document view of a space — but rather than defining the space, it reflects it, querying and assembling information from the space's node files. Useful after a space has "graduated" from a single `space on a page` file to a `space directory`, as a way to preserve that top-level overview. The dashboard concept may evolve to surface more operational information over time, but there is no concrete design for that yet.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Hierarchy
|
|
158
|
+
|
|
159
|
+
The **hierarchy** is the ordered list of node types in a space, from root to leaf. It is defined in the schema's `$metadata.hierarchy.levels` array and drives depth-based type inference (for `space on a page`), tree rendering, and structural validation. The root type has no parent; every other type has parents in the level immediately above (unless `$metadata.hierarchy.allowSkipLevels` is set).
|
|
160
|
+
|
|
161
|
+
The hierarchy is modelled as a layered DAG: a non-root node may have zero parents (orphaned), one parent, or multiple parents. The `show` command renders this as an indented tree, marking repeated nodes with `(*)` where the subtree is already shown elsewhere.
|
|
162
|
+
|
|
163
|
+
Each non-root level uses the shared `field`, `fieldOn`, and `multiple` edge options (see [Graph edges](#graph-edges)). Hierarchy-specific options:
|
|
164
|
+
|
|
165
|
+
| Option | Default | Meaning |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `selfRef` | `false` | When `true`, a node may have a parent of the same resolved type, using `field` for both regular and same-type parents |
|
|
168
|
+
| `selfRefField` | _undefined_ | When set, specifies a separate field for same-type parent relationships (always on the child). Requires `selfRef: true`. |
|
|
169
|
+
| `templateFormat` | _undefined_ | Embedding hint (`"list"`, `"table"`, `"heading"`). When set alongside `matchers`, enables hierarchy embedding in typed pages |
|
|
170
|
+
| `matchers` | _undefined_ | Heading patterns (strings or `/regex/`) that signal this level's embedding section |
|
|
171
|
+
| `embeddedTemplateFields` | _undefined_ | Column names used when `template-sync` generates table stubs for this level |
|
|
172
|
+
|
|
173
|
+
### Hierarchy embedding
|
|
174
|
+
|
|
175
|
+
Hierarchy levels with `templateFormat` and `matchers` support **embedded parsing** analogous to relationships: a heading in a typed page that matches the level's `matchers` signals that following content (list items or table rows) belongs to that type, without requiring explicit `[type:: x]` annotations.
|
|
176
|
+
|
|
177
|
+
Two sub-patterns:
|
|
178
|
+
|
|
179
|
+
- **Child-level embedding** — heading matches the next level in hierarchy → items create new child nodes.
|
|
180
|
+
- **Parent-level referencing** — heading matches the level *above* the current node's type → bare wikilink items (`- [[X]]`) populate the current node's field pointing up to that parent type. This lets a node list its parent references inline without creating duplicate nodes.
|
|
181
|
+
|
|
182
|
+
Bare wikilink items (`- [[X]]`) in any embedding section always populate a field rather than creating new nodes.
|
|
183
|
+
|
|
184
|
+
**Example: Activities listing Capabilities with sub-capabilities**
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
"levels": [
|
|
188
|
+
"Activities",
|
|
189
|
+
{
|
|
190
|
+
"type": "Capabilities",
|
|
191
|
+
"field": "capabilities",
|
|
192
|
+
"fieldOn": "parent",
|
|
193
|
+
"multiple": true,
|
|
194
|
+
"selfRefField": "parent"
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This defines two edge types for Capabilities:
|
|
200
|
+
- **Activities → Capabilities**: Via `capabilities` array field on Activity nodes
|
|
201
|
+
- **Capability → Capability**: Via `parent` field on Capability nodes (same-type, child-side)
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Relationships
|
|
206
|
+
|
|
207
|
+
A **relationship** is a link between a parent type and a child type that is not part of the primary structural hierarchy. For example, an `opportunity` might have a relationship with `assumption` (multiple) or `problem_statement` (single).
|
|
208
|
+
|
|
209
|
+
Relationships are defined in `$metadata.relationships`. Like hierarchy levels, they use the shared `field`, `fieldOn`, and `multiple` edge options (see [Graph edges](#graph-edges)), but carry additional metadata used for parsing and template generation:
|
|
210
|
+
|
|
211
|
+
- **`parent`** / **`type`** — the parent and child canonical types (required)
|
|
212
|
+
- **`templateFormat`** — parsing/generation hint: `"heading"`, `"list"`, `"table"`, or `"page"`
|
|
213
|
+
- **`matchers`** — heading text patterns (strings or `/regex/`) used to detect relationship sections during embedded parsing
|
|
214
|
+
|
|
215
|
+
A heading in a typed page that matches a relationship's `matchers` signals to the parser that following content (single nodes, list items, or table rows) should be typed as that relationship's child type — without requiring explicit inline `[type:: x]` annotations.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Graph edges
|
|
220
|
+
|
|
221
|
+
Both `hierarchy.levels` and `relationships` define **edges** in a directed graph over the node set. All edges use the same three configuration options:
|
|
222
|
+
|
|
223
|
+
| Option | Default | Meaning |
|
|
224
|
+
|---|---|---|
|
|
225
|
+
| `field` | `"parent"` | The frontmatter field holding the wikilink(s) |
|
|
226
|
+
| `fieldOn` | `"child"` | `"parent"` means the field is on the **parent** node and points to children (reversed direction) |
|
|
227
|
+
| `multiple` | `false` | When `true`, the field holds an **array** of wikilinks rather than a single one |
|
|
228
|
+
|
|
229
|
+
The `fieldOn: "parent"` pattern is used when the content model lists children on the parent node (e.g. `tasks: ["[[Task A]]", "[[Task B]]"]`). Embedded parsing then appends child wikilinks to the parent's field array rather than setting a `parent` field on each child.
|
|
230
|
+
|
|
231
|
+
Dangling wikilinks — edge field values that do not resolve to any known node — are reported as reference errors during validation.
|
|
232
|
+
|
|
233
|
+
### Wikilink
|
|
234
|
+
|
|
235
|
+
A **wikilink** is the `[[Title]]` linking syntax (compatible with Obsidian) used in edge fields to reference other `space nodes`. Two forms are supported:
|
|
236
|
+
|
|
237
|
+
| Form | Example | Resolves to |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| Plain title | `[[My Goal]]` | The `space node` whose title equals `My Goal` |
|
|
240
|
+
| Anchor ref | `[[vision_page#^goal1]]` | The `embedded node` with `anchor` `goal1` inside `vision_page.md` |
|
|
241
|
+
|
|
242
|
+
### Resolved parents
|
|
243
|
+
|
|
244
|
+
**Resolved parents** (`resolvedParents`) is the set of parent references derived from a node's edge fields at *parse* time. It is always an array (empty if unresolved or root-level). Both `hierarchy.levels` and `relationships` edges resolve into this single array — forming a unified labelled directed graph over the node set.
|
|
245
|
+
|
|
246
|
+
Each entry is a `ResolvedParentRef` object:
|
|
247
|
+
|
|
248
|
+
| Field | Type | Description |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `title` | `string` | The parent node's title |
|
|
251
|
+
| `field` | `string` | The frontmatter field that held the wikilink |
|
|
252
|
+
| `source` | `'hierarchy' \| 'relationship'` | Whether the edge came from a hierarchy level or a relationship |
|
|
253
|
+
| `selfRef` | `boolean` | Whether the edge is a same-type (self-referential) parent link |
|
|
254
|
+
|
|
255
|
+
The `source` label lets downstream consumers distinguish edge types without re-inspecting the schema. Validation routes `hierarchy` edges to structural checks (parent-type rules, skip-level detection) and `relationship` edges to field reference checks (type-match, missing-target). Tree rendering and rule evaluation use the full set.
|
|
256
|
+
|
|
257
|
+
### Anchor
|
|
258
|
+
|
|
259
|
+
An **anchor** is a block anchor (e.g. `^goal1`) appended to a heading in a `typed page`, using Obsidian block anchor syntax. Anchors serve two purposes:
|
|
260
|
+
|
|
261
|
+
1. **Cross-file references** — other files can reference an `embedded node` by `[[filename#^anchor]]`.
|
|
262
|
+
2. **Anchor-implied type** — if the anchor name matches a node type name or a node type name followed by digits (e.g. `^mission`, `^goal1`), the node's type is inferred from the anchor, making an explicit inline annotation unnecessary.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Filter expressions
|
|
267
|
+
|
|
268
|
+
A **filter expression** is a string that selects a subset of nodes from a space. Filter expressions are used with the `--filter` flag on the `show` command and in named filter views in config.
|
|
269
|
+
|
|
270
|
+
### Syntax
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
WHERE {jsonata} — return nodes where the JSONata predicate is truthy
|
|
274
|
+
SELECT {spec} WHERE {jsonata} — filter by WHERE, then expand result via SELECT
|
|
275
|
+
SELECT {spec} — expand from all nodes (no WHERE filter)
|
|
276
|
+
{jsonata} — bare JSONata, treated as a WHERE predicate (convenience shorthand)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Keywords (`WHERE`, `SELECT`) are case-insensitive.
|
|
280
|
+
|
|
281
|
+
### Predicate context
|
|
282
|
+
|
|
283
|
+
The WHERE predicate is a [JSONata](https://docs.jsonata.org/overview) expression evaluated per node. Node fields (from `schemaData`) are accessible directly — e.g. `resolvedType`, `status`, `title`. Additionally, two pre-computed traversal arrays are available:
|
|
284
|
+
|
|
285
|
+
| Field | Description |
|
|
286
|
+
|-------|-------------|
|
|
287
|
+
| `ancestors[]` | Flat array of ancestor nodes, nearest first, deduplicated by title |
|
|
288
|
+
| `descendants[]` | Flat array of descendant nodes, nearest first, deduplicated by title |
|
|
289
|
+
|
|
290
|
+
Each entry in `ancestors[]` or `descendants[]` includes all schema fields of the target node, plus edge metadata:
|
|
291
|
+
|
|
292
|
+
| Metadata field | Type | Description |
|
|
293
|
+
|----------------|------|-------------|
|
|
294
|
+
| `_field` | `string` | The edge field name that connects to this ancestor/descendant |
|
|
295
|
+
| `_source` | `'hierarchy' \| 'relationship'` | Whether the edge came from the hierarchy or a relationship |
|
|
296
|
+
| `_selfRef` | `boolean` | Whether the edge is a same-type (self-referential) link |
|
|
297
|
+
|
|
298
|
+
### SELECT spec
|
|
299
|
+
|
|
300
|
+
The SELECT clause expands the result set by walking the graph from matched nodes. The spec is a comma-separated list of directives:
|
|
301
|
+
|
|
302
|
+
| Directive | Meaning |
|
|
303
|
+
|-----------|---------|
|
|
304
|
+
| `ancestors` | All ancestor nodes |
|
|
305
|
+
| `ancestors(type)` | Ancestors of the given resolved type |
|
|
306
|
+
| `descendants` | All descendant nodes |
|
|
307
|
+
| `descendants(type)` | Descendants of the given resolved type |
|
|
308
|
+
| `siblings` | Nodes sharing at least one parent with matched nodes |
|
|
309
|
+
| `relationships` | Nodes connected via a relationship (non-hierarchy) edge |
|
|
310
|
+
| `relationships(childType)` | Relationship-connected nodes of the given child type |
|
|
311
|
+
| `relationships(parentType:childType)` | As above, also filtering by parent type |
|
|
312
|
+
| `relationships(parentType:field:childType)` | Fully qualified: also filtering by edge field name |
|
|
313
|
+
|
|
314
|
+
Multiple directives may be combined with commas: `SELECT ancestors(goal), siblings WHERE ...`
|
|
315
|
+
|
|
316
|
+
### Examples
|
|
317
|
+
|
|
318
|
+
```jsonata
|
|
319
|
+
WHERE resolvedType='solution' and status='active'
|
|
320
|
+
|
|
321
|
+
WHERE resolvedType='solution' and $exists(ancestors[resolvedType='opportunity' and status='active'])
|
|
322
|
+
|
|
323
|
+
WHERE $count(descendants[resolvedType='solution']) > 3
|
|
324
|
+
|
|
325
|
+
SELECT ancestors(opportunity) WHERE resolvedType='solution'
|
|
326
|
+
|
|
327
|
+
SELECT siblings WHERE resolvedType='solution' and status='active'
|
|
328
|
+
|
|
329
|
+
SELECT relationships(assumption) WHERE resolvedType='opportunity'
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Filter views
|
|
335
|
+
|
|
336
|
+
A **filter view** is a named filter expression defined in the space config. Views allow commonly used filters to be referenced by name rather than repeating the expression inline.
|
|
337
|
+
|
|
338
|
+
Views are defined in the space config under the `views` key:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"spaces": [
|
|
343
|
+
{
|
|
344
|
+
"name": "my-space",
|
|
345
|
+
"path": "/path/to/space",
|
|
346
|
+
"views": {
|
|
347
|
+
"active-solutions": {
|
|
348
|
+
"expression": "WHERE resolvedType='solution' and status='active'"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Use a view by name with `sctx show <space> --filter <view-name>`. If no matching view name is found in the config, the value is treated as an inline filter expression.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Status
|
|
361
|
+
|
|
362
|
+
**Status** is a lifecycle field on nodes indicating a node's current stage. The valid values and their semantics are defined by the schema in use. Examples from the default schema (in rough progression):
|
|
363
|
+
|
|
364
|
+
`identified` → `wondering` → `exploring` → `active` → `paused` → `completed` → `archived`
|
|
365
|
+
|
|
366
|
+
Status is required on all node types at _validation_ time. Note however that currently the `space on a page` parser chooses to apply a default.
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Plugin
|
|
372
|
+
|
|
373
|
+
A **plugin** is a module that extends the tool's capabilities.
|
|
374
|
+
|
|
375
|
+
Plugins that support parsing produce raw `SpaceNode[]` results given a suitable configuration. Graph edge resolution (`resolveGraphEdges`) is called by the core afterwards.
|
|
376
|
+
|
|
377
|
+
The `plugins` field in config is a **map** from plugin name to plugin config object. All plugin names must start with `sctx-`, but the prefix is optional in config and normalised on load. Built-in plugins take precedence and all are loaded by default. External plugin names specified in config are then resolved in order:
|
|
378
|
+
|
|
379
|
+
1. Config-adjacent: `{configDir}/plugins/{sctx-name}`
|
|
380
|
+
2. npm: a package matching `sctx-*`
|
|
381
|
+
|
|
382
|
+
Each plugin declares a `configSchema` JSON Schema; the loader validates the config block against it before invoking the plugin. Config fields annotated with `format: 'path'` are resolved relative to `configDir` by the loader.
|
|
383
|
+
|
|
384
|
+
### Dispatch contract
|
|
385
|
+
|
|
386
|
+
Plugins are called in order (external plugins first, then built-ins). For each operation:
|
|
387
|
+
- A plugin that does not implement the hook is skipped.
|
|
388
|
+
- A plugin that implements the hook returns `T | null`: non-null means "I handled it"; null means "not me, try the next plugin."
|
|
389
|
+
- The first non-null result wins. If no plugin handles the operation, the command throws an error.
|
|
390
|
+
|
|
391
|
+
A plugin that can handle a space should do so and return a result. Conversely, a plugin should return null if it inspects the context and determines the operation is not meant for it (for example, a plugin that only handles a specific file format or URL).
|
package/docs/config.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# structured-context Configuration Reference
|
|
2
|
+
|
|
3
|
+
## Config file location
|
|
4
|
+
|
|
5
|
+
structured-context looks for its config file in this order:
|
|
6
|
+
|
|
7
|
+
1. `$SCTX_CONFIG` — explicit path override
|
|
8
|
+
2. `~/.config/structured-context/config.json` (or `$XDG_CONFIG_HOME/structured-context/config.json`)
|
|
9
|
+
3. `./config.json` in the current working directory
|
|
10
|
+
|
|
11
|
+
See `config.example.json` for the full structure. Paths in config files are resolved relative to the config file.
|
|
12
|
+
|
|
13
|
+
## Spaces
|
|
14
|
+
|
|
15
|
+
A space is a named directory or single file registered in the config. Example:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"spaces": [
|
|
20
|
+
{
|
|
21
|
+
"name": "ProductX",
|
|
22
|
+
"path": "/path/to/space",
|
|
23
|
+
"schema": "general.json"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**`includeSpacesFrom`** — import space definitions from other config files. Useful for aggregating spaces from multiple projects into a central config. Duplicate space names are not allowed.
|
|
30
|
+
|
|
31
|
+
## Plugins
|
|
32
|
+
|
|
33
|
+
Use `plugins` to load parse plugins that read spaces from non-markdown sources. The built-in markdown plugin is always available without any declaration. Plugins are tried in order; the first to return a result wins. The `plugins` field is a map of plugin name to plugin config, and can be declared at the top level (applies to all spaces) or per-space (overrides the top level):
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"spaces": [
|
|
38
|
+
{
|
|
39
|
+
"name": "ProductX",
|
|
40
|
+
"path": "/path/to/space",
|
|
41
|
+
"plugins": {
|
|
42
|
+
"markdown": { "fieldMap": { "record_type": "type" } }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"plugins": {
|
|
47
|
+
"sctx-confluence": { "baseUrl": "https://example.atlassian.net" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
All plugin names must start with `sctx-` (the prefix is optional in config and normalised on load). The special name `markdown` refers to the built-in markdown plugin. External plugins are resolved in order: config-adjacent (`{configDir}/plugins/{name}`), then npm. Each plugin must export a `configSchema` JSON Schema; config is validated against it on load. Fields annotated `format: 'path'` in a plugin's `configSchema` are resolved relative to the config file directory.
|
|
53
|
+
|
|
54
|
+
## Markdown plugin config
|
|
55
|
+
|
|
56
|
+
Set under `plugins.markdown` per space.
|
|
57
|
+
|
|
58
|
+
### `fieldMap`
|
|
59
|
+
|
|
60
|
+
Maps file/frontmatter field names to canonical schema field names:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{ "fieldMap": { "record_type": "type" } }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `templateDir` and `templatePrefix`
|
|
67
|
+
|
|
68
|
+
- `templateDir` — directory containing template files (used by `template-sync` and excluded when parsing)
|
|
69
|
+
- `templatePrefix` — filename prefix for templates (default blank)
|
|
70
|
+
|
|
71
|
+
### `typeInference`
|
|
72
|
+
|
|
73
|
+
Automatically assigns a node type based on folder structure when no `type` field is present in frontmatter. Explicit `type:` always takes precedence.
|
|
74
|
+
|
|
75
|
+
**`mode`** — controls the matching strategy:
|
|
76
|
+
- `"folder-name"` (default) — matches the leaf directory name case-insensitively against schema type names and alias keys
|
|
77
|
+
- `"off"` — disables inference entirely
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"plugins": {
|
|
82
|
+
"markdown": {
|
|
83
|
+
"typeInference": { "mode": "folder-name" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**`folderMap`** — explicit map from folder path (relative to space root) to type name or alias. When set, replaces auto-matching entirely; only folders listed in the map are inferred.
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"plugins": {
|
|
94
|
+
"markdown": {
|
|
95
|
+
"typeInference": {
|
|
96
|
+
"folderMap": {
|
|
97
|
+
"Research": "source",
|
|
98
|
+
"Personal": "note",
|
|
99
|
+
"topics/concepts": "concept"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Longest-prefix matching is used when keys overlap (e.g. `a/b` and `a/b/c` both present). Trailing slashes in keys are normalised. Values may be type aliases (resolved to canonical type). An unresolvable value throws a hard error at parse time.
|
|
108
|
+
|
|
109
|
+
## Filter views
|
|
110
|
+
|
|
111
|
+
Named filter expressions can be defined per space under `views`. Each view has an `expression` field:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"spaces": [
|
|
116
|
+
{
|
|
117
|
+
"name": "my-space",
|
|
118
|
+
"path": "/path/to/space",
|
|
119
|
+
"views": {
|
|
120
|
+
"active-solutions": {
|
|
121
|
+
"expression": "WHERE resolvedType='solution' and status='active'"
|
|
122
|
+
},
|
|
123
|
+
"solutions-under-active-opportunity": {
|
|
124
|
+
"expression": "WHERE resolvedType='solution' and $exists(ancestors[resolvedType='opportunity' and status='active'])"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Use a view name with `sctx show <space> --filter <view-name>`.
|
|
133
|
+
|
|
134
|
+
See `sctx docs concepts` for full filter expression syntax.
|
|
135
|
+
|
|
136
|
+
## Security notice
|
|
137
|
+
|
|
138
|
+
**⚠️ Only use schemas and configuration files from trusted sources.**
|
|
139
|
+
|
|
140
|
+
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.
|