structured-context 0.9.1 → 0.9.2
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 +2 -2
- package/dist/commands/spaces.js +3 -2
- package/dist/commands/validate.js +15 -10
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -2
- package/dist/plugin-api.d.ts +1 -1
- package/dist/plugins/markdown/read-space.js +35 -8
- package/dist/plugins/util.d.ts +3 -3
- package/dist/read/read-space.js +1 -1
- package/dist/schemas/{general.json → strategy_general.json} +1 -1
- package/dist/types.d.ts +14 -2
- package/docs/config.md +1 -1
- package/docs/schemas.md +2 -2
- package/package.json +1 -1
- package/schemas/{general.json → strategy_general.json} +1 -1
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ If no provider defines `hierarchy`, hierarchy-specific checks are skipped. Readi
|
|
|
193
193
|
- **Transitive resolution**: `$ref` chains are resolved recursively across files/schemas (including nested `allOf` usage in partials).
|
|
194
194
|
- **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.
|
|
195
195
|
|
|
196
|
-
Schema resolution order: space config `schema` > global config `schema
|
|
196
|
+
Schema resolution order: space config `schema` > global config `schema`. Schema resolution fails if none is configured.
|
|
197
197
|
|
|
198
198
|
**⚠️ Security Notice: Only use schemas and configuration files from trusted sources.**
|
|
199
199
|
|
|
@@ -322,7 +322,7 @@ Generates a Mermaid Entity Relationship Diagram from a schema:
|
|
|
322
322
|
|
|
323
323
|
Example:
|
|
324
324
|
```bash
|
|
325
|
-
sctx schemas show
|
|
325
|
+
sctx schemas show strategy_general --mermaid-erd
|
|
326
326
|
```
|
|
327
327
|
|
|
328
328
|
### Sync space to Miro
|
package/dist/commands/spaces.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { basename, resolve } from 'node:path';
|
|
2
|
-
import { configPath, loadConfig
|
|
2
|
+
import { configPath, loadConfig } from '../config';
|
|
3
3
|
function renderConfigValue(v) {
|
|
4
4
|
if (typeof v === 'object' && v !== null)
|
|
5
5
|
return JSON.stringify(v);
|
|
@@ -12,7 +12,8 @@ export function listSpaces() {
|
|
|
12
12
|
for (const space of config.spaces) {
|
|
13
13
|
console.log(`${space.name}`);
|
|
14
14
|
console.log(` path: ${space.path}`);
|
|
15
|
-
|
|
15
|
+
const schema = space.schema ?? config.schema;
|
|
16
|
+
console.log(` schema: ${schema ? basename(schema) : '(none)'}`);
|
|
16
17
|
if (space.miroBoardId)
|
|
17
18
|
console.log(` miro: configured`);
|
|
18
19
|
const plugins = space.plugins ?? {};
|
|
@@ -97,7 +97,7 @@ export async function validate(context, options = {}) {
|
|
|
97
97
|
const { schema, schemaRefRegistry, schemaValidator } = context;
|
|
98
98
|
const metadata = schema.metadata;
|
|
99
99
|
const readResult = await readSpace(context);
|
|
100
|
-
const { nodes,
|
|
100
|
+
const { nodes, parseIssues } = readResult;
|
|
101
101
|
const result = {
|
|
102
102
|
validCount: 0,
|
|
103
103
|
nodeErrorCount: 0,
|
|
@@ -107,7 +107,7 @@ export async function validate(context, options = {}) {
|
|
|
107
107
|
ruleViolations: [],
|
|
108
108
|
hierarchyViolations: [],
|
|
109
109
|
orphans: [],
|
|
110
|
-
|
|
110
|
+
parseIssues,
|
|
111
111
|
};
|
|
112
112
|
for (const node of nodes) {
|
|
113
113
|
const valid = schemaValidator(node.schemaData);
|
|
@@ -180,7 +180,8 @@ export async function validate(context, options = {}) {
|
|
|
180
180
|
for (const v of result.hierarchyViolations) {
|
|
181
181
|
addError(v.file, `hierarchy:${v.description}`, 'hierarchy', v.description);
|
|
182
182
|
}
|
|
183
|
-
const
|
|
183
|
+
const parseErrorCount = result.parseIssues.filter((i) => i.severity === 'error').length;
|
|
184
|
+
const errorCount = Object.values(errorsByFile).reduce((sum, errs) => sum + Object.keys(errs).length, 0) + parseErrorCount;
|
|
184
185
|
console.log(JSON.stringify({
|
|
185
186
|
space: context.space.name,
|
|
186
187
|
valid: errorCount === 0,
|
|
@@ -188,7 +189,7 @@ export async function validate(context, options = {}) {
|
|
|
188
189
|
errorCount,
|
|
189
190
|
errors: errorsByFile,
|
|
190
191
|
orphanCount: result.orphans.length,
|
|
191
|
-
|
|
192
|
+
parseIssues: result.parseIssues,
|
|
192
193
|
}, null, 2));
|
|
193
194
|
return errorCount > 0 ? 1 : 0;
|
|
194
195
|
}
|
|
@@ -224,16 +225,19 @@ export async function validate(context, options = {}) {
|
|
|
224
225
|
console.log(fmt(' Rule violations', result.ruleViolations.length, true));
|
|
225
226
|
console.log(fmt(' Hierarchy violations', result.hierarchyViolations.length, true));
|
|
226
227
|
console.log(fmt(' Orphans (hierarchy nodes - no parent)', result.orphans.length, true, true));
|
|
227
|
-
|
|
228
|
+
const parseIssueErrorCount = result.parseIssues.filter((i) => i.severity === 'error').length;
|
|
229
|
+
console.log(fmt(' Excluded during parsing', result.parseIssues.length, true, parseIssueErrorCount === 0));
|
|
228
230
|
if (result.orphans.length > 0) {
|
|
229
231
|
console.log(`\nOrphans (hierarchy nodes - no parent):`);
|
|
230
232
|
for (const node of result.orphans)
|
|
231
233
|
console.log(` ${node.label}`);
|
|
232
234
|
}
|
|
233
|
-
if (result.
|
|
234
|
-
console.log(`\
|
|
235
|
-
for (const
|
|
236
|
-
|
|
235
|
+
if (result.parseIssues.length > 0) {
|
|
236
|
+
console.log(`\nExcluded during parsing:`);
|
|
237
|
+
for (const issue of result.parseIssues) {
|
|
238
|
+
const detail = issue.message ? ` - ${issue.message}` : '';
|
|
239
|
+
console.log(` ${issue.file}: ${issue.severity} - ${issue.type}${detail}`);
|
|
240
|
+
}
|
|
237
241
|
}
|
|
238
242
|
if (result.nodeErrors.length > 0) {
|
|
239
243
|
console.log(`\nSchema validation errors:`);
|
|
@@ -290,7 +294,8 @@ export async function validate(context, options = {}) {
|
|
|
290
294
|
result.refErrors.length > 0 ||
|
|
291
295
|
result.duplicateErrors.length > 0 ||
|
|
292
296
|
result.ruleViolations.length > 0 ||
|
|
293
|
-
result.hierarchyViolations.length > 0
|
|
297
|
+
result.hierarchyViolations.length > 0 ||
|
|
298
|
+
parseIssueErrorCount > 0) {
|
|
294
299
|
return 1;
|
|
295
300
|
}
|
|
296
301
|
return 0;
|
package/dist/config.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare function configPath(): string;
|
|
|
28
28
|
export declare function loadConfig(): Config;
|
|
29
29
|
/** Get the full space config entry by name. Throws if not found. */
|
|
30
30
|
export declare function getSpaceConfig(name: string, config: Config): SpaceConfig;
|
|
31
|
-
/** Resolve schema path: CLI arg > space-level config > global config
|
|
31
|
+
/** Resolve schema path: CLI arg > space-level config > global config. Throws if none configured. */
|
|
32
32
|
export declare function resolveSchema(config: Config, space?: SpaceConfig): string;
|
|
33
33
|
type StringFields<T> = {
|
|
34
34
|
[K in keyof T]: T[K] extends string | undefined ? K : never;
|
package/dist/config.js
CHANGED
|
@@ -170,9 +170,13 @@ export function getSpaceConfig(name, config) {
|
|
|
170
170
|
}
|
|
171
171
|
return space;
|
|
172
172
|
}
|
|
173
|
-
/** Resolve schema path: CLI arg > space-level config > global config
|
|
173
|
+
/** Resolve schema path: CLI arg > space-level config > global config. Throws if none configured. */
|
|
174
174
|
export function resolveSchema(config, space) {
|
|
175
|
-
|
|
175
|
+
const schema = space?.schema ?? config.schema;
|
|
176
|
+
if (!schema) {
|
|
177
|
+
throw new Error('No schema configured. Set "schema" in the space config or at the top level of the config file.');
|
|
178
|
+
}
|
|
179
|
+
return schema;
|
|
176
180
|
}
|
|
177
181
|
/** Update a string field on a space entry and persist config. */
|
|
178
182
|
export function updateSpaceField(spaceName, field, value) {
|
package/dist/plugin-api.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export type { AnySchemaObject, SchemaObject, ValidateFunction } from 'ajv';
|
|
|
9
9
|
export type { ParseHook, ParseResult, PluginContext, RenderFormat, RenderHook, RenderOptions, StructuredContextPlugin, TemplateSyncHook, TemplateSyncOptions, } from './plugins/util';
|
|
10
10
|
export type { SharedEmbeddingFields } from './schema/metadata-contract';
|
|
11
11
|
export type { SpaceGraph } from './space-graph';
|
|
12
|
-
export type { BaseNode, EdgeDefinition, HierarchyLevel, Relationship, SchemaMetadata, SchemaWithMetadata, SpaceContext, SpaceNode, UnresolvedRef, } from './types';
|
|
12
|
+
export type { BaseNode, EdgeDefinition, HierarchyLevel, ParseIssue, Relationship, SchemaMetadata, SchemaWithMetadata, SpaceContext, SpaceNode, UnresolvedRef, } from './types';
|
|
@@ -25,7 +25,13 @@ export function readSpaceOnAPage(context) {
|
|
|
25
25
|
pageType: 'space_on_a_page',
|
|
26
26
|
metadata,
|
|
27
27
|
});
|
|
28
|
-
|
|
28
|
+
const parseIssues = terminatedHeadings.map((heading) => ({
|
|
29
|
+
file: heading,
|
|
30
|
+
severity: 'warning',
|
|
31
|
+
type: 'terminated',
|
|
32
|
+
message: 'Ignored headings detected beyond end of hierarchy.',
|
|
33
|
+
}));
|
|
34
|
+
return { nodes, parseIssues, diagnostics: { kind: 'page', preambleNodeCount } };
|
|
29
35
|
}
|
|
30
36
|
export async function readSpaceDirectory(context, options) {
|
|
31
37
|
const { space, schema: { metadata }, } = context;
|
|
@@ -38,17 +44,33 @@ export async function readSpaceDirectory(context, options) {
|
|
|
38
44
|
const knownTypes = typeInferenceCfg?.mode !== 'off' ? extractSchemaTypeNames(context.schema, context.schemaRefRegistry) : undefined;
|
|
39
45
|
const files = await Array.fromAsync(new Glob('**/*.md').scan({ cwd: directory, followSymlinks: true }));
|
|
40
46
|
const nodes = [];
|
|
41
|
-
const
|
|
42
|
-
const nonSpace = [];
|
|
47
|
+
const parseIssues = [];
|
|
43
48
|
for (const file of files) {
|
|
44
49
|
const absoluteFilePath = resolve(directory, file);
|
|
45
50
|
if (absoluteTemplateDir && absoluteFilePath.startsWith(absoluteTemplateDir)) {
|
|
46
51
|
continue;
|
|
47
52
|
}
|
|
48
53
|
const content = readFileSync(join(directory, file), 'utf-8');
|
|
49
|
-
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = matter(content);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// gray-matter caches parsed YAML and has a known bug where a caught exception
|
|
60
|
+
// corrupts its internal cache, causing subsequent parses to silently return {}.
|
|
61
|
+
// Clear the cache after any parse error to avoid stale state.
|
|
62
|
+
// See: https://github.com/jonschlinkert/gray-matter/issues/166
|
|
63
|
+
matter.clearCache();
|
|
64
|
+
parseIssues.push({
|
|
65
|
+
file,
|
|
66
|
+
severity: 'error',
|
|
67
|
+
type: 'parse',
|
|
68
|
+
message: err instanceof Error ? err.message : String(err),
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
50
72
|
if (!parsed.data || Object.keys(parsed.data).length === 0) {
|
|
51
|
-
|
|
73
|
+
parseIssues.push({ file, severity: 'warning', type: 'empty' });
|
|
52
74
|
continue;
|
|
53
75
|
}
|
|
54
76
|
const data = coerceDates(applyFieldMap(parsed.data, fieldMap));
|
|
@@ -56,7 +78,7 @@ export async function readSpaceDirectory(context, options) {
|
|
|
56
78
|
data.type = inferTypeFromPath(file, typeInferenceCfg, knownTypes, context.schema.metadata.typeAliases);
|
|
57
79
|
}
|
|
58
80
|
if (!data.type) {
|
|
59
|
-
|
|
81
|
+
parseIssues.push({ file, severity: 'warning', type: 'no-type' });
|
|
60
82
|
continue;
|
|
61
83
|
}
|
|
62
84
|
if (ON_A_PAGE_TYPES.includes(data.type) && !options?.includeOnAPageFiles) {
|
|
@@ -81,9 +103,14 @@ export async function readSpaceDirectory(context, options) {
|
|
|
81
103
|
});
|
|
82
104
|
nodes.push(...embedded);
|
|
83
105
|
for (const heading of terminatedHeadings) {
|
|
84
|
-
|
|
106
|
+
parseIssues.push({
|
|
107
|
+
file: `${file} > ${heading}`,
|
|
108
|
+
severity: 'warning',
|
|
109
|
+
type: 'terminated',
|
|
110
|
+
message: 'Ignored headings detected beyond end of hierarchy.',
|
|
111
|
+
});
|
|
85
112
|
}
|
|
86
113
|
}
|
|
87
114
|
}
|
|
88
|
-
return { nodes,
|
|
115
|
+
return { nodes, parseIssues, diagnostics: { kind: 'directory' } };
|
|
89
116
|
}
|
package/dist/plugins/util.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AnySchemaObject } from 'ajv';
|
|
2
2
|
import type { SpaceGraph } from '../space-graph';
|
|
3
|
-
import type { BaseNode, SpaceContext } from '../types';
|
|
3
|
+
import type { BaseNode, ParseIssue, SpaceContext } from '../types';
|
|
4
4
|
export declare const PLUGIN_PREFIX = "sctx-";
|
|
5
5
|
export declare const CONFIG_PLUGINS_DIR = "plugins";
|
|
6
6
|
/** Normalize a plugin name to its canonical prefixed form. */
|
|
@@ -11,8 +11,8 @@ export type PluginContext = SpaceContext & {
|
|
|
11
11
|
};
|
|
12
12
|
export type ParseResult = {
|
|
13
13
|
nodes: BaseNode[];
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/** Issues encountered while parsing files — excluded files with reason and severity. */
|
|
15
|
+
parseIssues: ParseIssue[];
|
|
16
16
|
/** Plugin diagnostics: keyed scalar or list values. */
|
|
17
17
|
diagnostics: Record<string, number | string | string[]>;
|
|
18
18
|
};
|
package/dist/read/read-space.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json",
|
|
3
|
-
"$id": "sctx://
|
|
3
|
+
"$id": "sctx://strategy_general",
|
|
4
4
|
"title": "Strategy and opportunity ladder",
|
|
5
5
|
"description": "Validates frontmatter for space node files",
|
|
6
6
|
"$metadata": {
|
package/dist/types.d.ts
CHANGED
|
@@ -86,6 +86,18 @@ export type SpaceNode = BaseNode & {
|
|
|
86
86
|
*/
|
|
87
87
|
resolvedParents: ResolvedParentRef[];
|
|
88
88
|
};
|
|
89
|
+
export type ParseIssue = {
|
|
90
|
+
/** Source identifier of the file or item (filename or heading title). */
|
|
91
|
+
file: string;
|
|
92
|
+
/** error: prevented the file from producing nodes; warning: file was silently excluded. */
|
|
93
|
+
severity: 'error' | 'warning';
|
|
94
|
+
/** empty: no content; no-type: no type field;
|
|
95
|
+
* parse: parse failure (syntax issue or unexpected content);
|
|
96
|
+
* plugins may choose to provide additional types. */
|
|
97
|
+
type: 'empty' | 'no-type' | 'parse' | string;
|
|
98
|
+
/** Human-readable detail. */
|
|
99
|
+
message?: string;
|
|
100
|
+
};
|
|
89
101
|
/** Rule categories for organizing executable validation rules */
|
|
90
102
|
export type RuleCategory = 'validation' | 'coherence' | 'workflow' | 'best-practice';
|
|
91
103
|
export type { Rule } from './schema/metadata-contract';
|
|
@@ -118,8 +130,8 @@ export type SchemaWithMetadata = SchemaObject & {
|
|
|
118
130
|
export type ReadSpaceResult = {
|
|
119
131
|
/** Fully resolved nodes produced by the plugin and enriched by core. */
|
|
120
132
|
nodes: SpaceNode[];
|
|
121
|
-
/**
|
|
122
|
-
|
|
133
|
+
/** Issues encountered while parsing files — excluded files with reason and severity. */
|
|
134
|
+
parseIssues: ParseIssue[];
|
|
123
135
|
/** Plugin diagnostics: keyed scalar or list values. */
|
|
124
136
|
diagnostics: Record<string, number | string | string[]>;
|
|
125
137
|
/** Name of the plugin that produced the nodes. */
|
package/docs/config.md
CHANGED
package/docs/schemas.md
CHANGED
|
@@ -20,11 +20,11 @@ Set `schema` in the space config entry:
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Resolution order: space `schema` > global `schema
|
|
23
|
+
Resolution order: space `schema` > global `schema`. Schema resolution fails if none is configured.
|
|
24
24
|
|
|
25
25
|
## Bundled schemas
|
|
26
26
|
|
|
27
|
-
### `
|
|
27
|
+
### `strategy_general.json`
|
|
28
28
|
|
|
29
29
|
Flexible planning schema spanning strategy + OST-like flow.
|
|
30
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json",
|
|
3
|
-
"$id": "sctx://
|
|
3
|
+
"$id": "sctx://strategy_general",
|
|
4
4
|
"title": "Strategy and opportunity ladder",
|
|
5
5
|
"description": "Validates frontmatter for space node files",
|
|
6
6
|
"$metadata": {
|