structured-context 0.9.2 → 0.10.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 CHANGED
@@ -55,12 +55,13 @@ Install globally (recommended):
55
55
 
56
56
  ```bash
57
57
  bun install -g structured-context
58
+ sctx --help
58
59
  ```
59
60
 
60
61
  Or use directly via `bunx`:
61
62
 
62
63
  ```bash
63
- bunx structured-context validate <space>
64
+ bunx structured-context --help
64
65
  ```
65
66
 
66
67
  ## Setup for AI Agents
@@ -179,15 +180,18 @@ Bare wikilink items (`- [[Existing Node]]`) in any embedding section populate a
179
180
  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]]"]`).
180
181
 
181
182
  Metadata is composable across `$ref` graphs:
182
- - zero or one metadata provider may define `hierarchy`
183
+ - `hierarchy`: multiple schema files may define a hierarchy; **last one wins** (root schema overrides partials)
183
184
  - `aliases` are shallow-merged (later wins)
184
185
  - `rules` merge by `id`; conflicts error unless the later rule sets `override: true`
185
186
  - `$metadata.rules` supports `$ref` imports for reusable rule packs
187
+ - `relationships` are collected from all files
186
188
 
187
- If no provider defines `hierarchy`, hierarchy-specific checks are skipped. Reading a `space_on_a_page` file still requires `hierarchy.levels`.
189
+ If no file defines `hierarchy`, hierarchy-specific checks are skipped.
188
190
 
189
191
  **Customizing Schemas:**
190
- - **Partial schemas**: Files starting with an underscore (like `_sctx_base.json`) are loaded and used to resolve references (using `$ref`).
192
+ - **Partial schemas**: Files starting with an underscore (like `_sctx_base.json`, `_strategy_general.json`, `_knowledge_wiki.json`) are loaded and used to resolve references (using `$ref`).
193
+ - **Partials as entity libraries**: Partials can define reusable entity types in `$defs` that composing schemas reference via `$ref`. Bundled partials like `_strategy_general` and `_knowledge_wiki` provide common entity sets for strategy and wiki content.
194
+ - **Partials can carry metadata**: Unlike plain JSON Schema, partials may include `$metadata` (hierarchy, aliases, relationships, rules). This makes them self-contained units that bundle both type definitions and behavioral metadata.
191
195
  - **No-metadata partials**: If a partial has no `$metadata`, prefer `$schema: "http://json-schema.org/draft-07/schema#"` so it validates standalone as plain JSON Schema.
192
196
  - **Loading priority**: Partial schemas are loaded from both the default schema directory and the directory of your specified target schema.
193
197
  - **Transitive resolution**: `$ref` chains are resolved recursively across files/schemas (including nested `allOf` usage in partials).
package/dist/api.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Public API for structured-context.
3
+ *
4
+ * Import from this module to use structured-context as a library:
5
+ *
6
+ * import { validateFile } from 'structured-context/api';
7
+ * import type { StructuredContextPlugin, PluginContext, ParseResult } from 'structured-context/api';
8
+ */
9
+ export type { AnySchemaObject, SchemaObject, ValidateFunction } from 'ajv';
10
+ export type { Config, SpaceConfig } from './config';
11
+ export type { ParseHook, ParseResult, PluginContext, RenderFormat, RenderHook, RenderOptions, StructuredContextPlugin, TemplateSyncHook, TemplateSyncOptions, } from './plugins/util';
12
+ export type { SharedEmbeddingFields } from './schema/metadata-contract';
13
+ export type { SpaceGraph } from './space-graph';
14
+ export type { BaseNode, EdgeDefinition, FileNotInSpaceResult, FileValidationResult, HierarchyLevel, ParseIssue, Relationship, SchemaMetadata, SchemaWithMetadata, SpaceContext, SpaceNode, UnresolvedRef, ValidateFileOutput, } from './types';
15
+ export { validateFile } from './validate';
package/dist/api.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public API for structured-context.
3
+ *
4
+ * Import from this module to use structured-context as a library:
5
+ *
6
+ * import { validateFile } from 'structured-context/api';
7
+ * import type { StructuredContextPlugin, PluginContext, ParseResult } from 'structured-context/api';
8
+ */
9
+ export { validateFile } from './validate';
@@ -1,4 +1,4 @@
1
- import { JSON5 } from 'bun';
1
+ import JSON5 from 'json5';
2
2
  import { readSpace } from '../read/read-space';
3
3
  export async function dump(context) {
4
4
  const { nodes, source, diagnostics } = await readSpace(context);
@@ -1,28 +1,9 @@
1
- export interface FileValidationResult {
2
- file: string;
3
- label: string;
4
- space: string;
5
- /** Errors keyed by composite id (e.g. `schema:/status:enum:active`, `rule:my-rule-id`). */
6
- errors: Record<string, {
7
- kind: string;
8
- message: string;
9
- }>;
10
- errorCount: number;
11
- inSpace: true;
12
- }
13
- export interface FileNotInSpaceResult {
14
- file: string;
15
- inSpace: false;
16
- message: string;
17
- }
18
- export type ValidateFileOutput = FileValidationResult | FileNotInSpaceResult;
1
+ export { validateFile } from '../validate';
19
2
  /**
20
- * Validate a single file within its space.
21
- *
22
- * Reads the full space (required for graph correctness) but filters all reported
23
- * errors to only those attributable to the target file. Exits 0 if the file is
24
- * not in any configured space (not an error — hooks call this on all file writes).
3
+ * CLI command: validate a file and print results to stdout.
4
+ * Loads config automatically from the environment, outputs human-readable or JSON,
5
+ * and returns an exit code (0 = valid, 1 = errors found).
25
6
  */
26
- export declare function validateFile(filePath: string, options?: {
7
+ export declare function validateFileCommand(filePath: string, options?: {
27
8
  json?: boolean;
28
9
  }): Promise<number>;
@@ -1,121 +1,27 @@
1
- import { isAbsolute, relative, resolve } from 'node:path';
2
- import { getSpaceConfigDir, loadConfig, resolveSchema } from '../config';
3
- import { readSpace } from '../read/read-space';
4
- import { loadSchema } from '../schema/schema';
5
- import { validateGraph } from '../schema/validate-graph';
6
- import { validateRules } from '../schema/validate-rules';
7
- import { formatErrors } from './validate';
8
- /** Find which space a file belongs to by checking directory containment. */
9
- function resolveFileSpace(filePath, config) {
10
- const absFile = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
11
- for (const space of config.spaces) {
12
- const absSpace = isAbsolute(space.path) ? space.path : resolve(process.cwd(), space.path);
13
- // Trailing slash prevents prefix-match false positives (e.g. /foo matching /foobar/)
14
- const spaceDir = absSpace.endsWith('/') ? absSpace : `${absSpace}/`;
15
- if (absFile.startsWith(spaceDir) || absFile === absSpace) {
16
- return { space, label: relative(absSpace, absFile) };
17
- }
18
- }
19
- return null;
20
- }
21
- function buildContextForSpace(space, config) {
22
- const resolvedSchemaPath = resolveSchema(config, space);
23
- const { schema, schemaRefRegistry, schemaValidator } = loadSchema(resolvedSchemaPath);
24
- const configDir = getSpaceConfigDir(space.name);
25
- return { space, config, resolvedSchemaPath, schema, schemaRefRegistry, schemaValidator, configDir };
26
- }
1
+ import { loadConfig } from '../config';
2
+ import { validateFile } from '../validate';
3
+ export { validateFile } from '../validate';
27
4
  /**
28
- * Validate a single file within its space.
29
- *
30
- * Reads the full space (required for graph correctness) but filters all reported
31
- * errors to only those attributable to the target file. Exits 0 if the file is
32
- * not in any configured space (not an error — hooks call this on all file writes).
5
+ * CLI command: validate a file and print results to stdout.
6
+ * Loads config automatically from the environment, outputs human-readable or JSON,
7
+ * and returns an exit code (0 = valid, 1 = errors found).
33
8
  */
34
- export async function validateFile(filePath, options = {}) {
9
+ export async function validateFileCommand(filePath, options = {}) {
35
10
  const config = loadConfig();
36
- const resolved = resolveFileSpace(filePath, config);
37
- if (!resolved) {
38
- const result = {
39
- file: filePath,
40
- inSpace: false,
41
- message: 'File does not belong to any configured space.',
42
- };
11
+ const result = await validateFile(filePath, config);
12
+ if (!result.inSpace) {
43
13
  if (options.json) {
44
14
  console.log(JSON.stringify(result, null, 2));
45
15
  }
46
16
  return 0;
47
17
  }
48
- const { space, label } = resolved;
49
- const context = buildContextForSpace(space, config);
50
- const { schema, schemaRefRegistry, schemaValidator } = context;
51
- const metadata = schema.metadata;
52
- const readResult = await readSpace(context);
53
- const { nodes } = readResult;
54
- const errors = {};
55
- // Schema validation errors for this node
56
- for (const node of nodes) {
57
- if (node.label !== label)
58
- continue;
59
- const valid = schemaValidator(node.schemaData);
60
- if (!valid) {
61
- const formatted = formatErrors(schemaValidator.errors ?? [], schema, schemaRefRegistry, node.schemaData);
62
- for (const { message, dedupeKey } of formatted) {
63
- errors[`schema:${dedupeKey}`] = { kind: 'schema', message };
64
- }
65
- }
66
- }
67
- // Duplicate key errors — include if this file is one of the duplicates
68
- const titleToFiles = new Map();
69
- for (const node of nodes) {
70
- if (!titleToFiles.has(node.title))
71
- titleToFiles.set(node.title, []);
72
- titleToFiles.get(node.title).push(node.label);
73
- }
74
- for (const [title, files] of titleToFiles) {
75
- if (files.length > 1 && files.includes(label)) {
76
- const others = files.filter((f) => f !== label);
77
- errors[`duplicate:${title}`] = {
78
- kind: 'duplicate',
79
- message: `Duplicate title "${title}" also exists in: ${others.join(', ')}`,
80
- };
81
- }
82
- }
83
- // Broken links and hierarchy violations from this file
84
- const hierarchyValidation = validateGraph(nodes, metadata, readResult.unresolvedRefs);
85
- for (const { file, parent, error } of hierarchyValidation.refErrors) {
86
- if (file === label) {
87
- errors[`broken-link:${parent}`] = { kind: 'broken-link', message: `${parent} → ${error}` };
88
- }
89
- }
90
- for (const v of hierarchyValidation.violations) {
91
- if (v.file === label) {
92
- errors[`hierarchy:${v.description}`] = { kind: 'hierarchy', message: v.description };
93
- }
94
- }
95
- // Rule violations for this node
96
- if (metadata.rules) {
97
- const ruleViolations = await validateRules(nodes, metadata.rules);
98
- for (const v of ruleViolations) {
99
- if (v.file === label) {
100
- errors[`rule:${v.ruleId}`] = { kind: 'rule', message: `[${v.ruleId}] ${v.description}` };
101
- }
102
- }
103
- }
104
- const result = {
105
- file: isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath),
106
- label,
107
- space: space.name,
108
- errors,
109
- errorCount: Object.keys(errors).length,
110
- inSpace: true,
111
- };
112
18
  if (options.json) {
113
19
  console.log(JSON.stringify(result, null, 2));
114
20
  }
115
21
  else {
116
22
  printHumanReadable(result);
117
23
  }
118
- return Object.keys(errors).length > 0 ? 1 : 0;
24
+ return Object.keys(result.errors).length > 0 ? 1 : 0;
119
25
  }
120
26
  function printHumanReadable(result) {
121
27
  const reset = '\x1b[0m';
@@ -1,15 +1,4 @@
1
- import type { ErrorObject } from 'ajv';
2
- import { extractEntityInfo } from '../schema/schema';
3
- import type { SchemaWithMetadata, SpaceContext } from '../types';
4
- export interface FormattedError {
5
- message: string;
6
- dedupeKey: string;
7
- }
8
- /**
9
- * Format AJV errors for better readability.
10
- * Groups related errors and extracts helpful context like allowed values.
11
- */
12
- export declare function formatErrors(errors: ErrorObject[], schema: SchemaWithMetadata, schemaRefRegistry: Parameters<typeof extractEntityInfo>[1], nodeData: Record<string, unknown>): FormattedError[];
1
+ import type { SpaceContext } from '../types';
13
2
  export declare function validate(context: SpaceContext, options?: {
14
3
  json?: boolean;
15
4
  }): Promise<number>;
@@ -1,154 +1,11 @@
1
1
  import { dirname } from 'node:path';
2
2
  import chokidar from 'chokidar';
3
3
  import { getConfigSourceFiles } from '../config';
4
- import { readSpace } from '../read/read-space';
5
- import { bundledSchemasDir, extractEntityInfo } from '../schema/schema';
6
- import { validateGraph } from '../schema/validate-graph';
7
- import { validateRules } from '../schema/validate-rules';
8
- import { buildSpaceGraph } from '../space-graph';
9
- /**
10
- * Format AJV errors for better readability.
11
- * Groups related errors and extracts helpful context like allowed values.
12
- */
13
- export function formatErrors(errors, schema, schemaRefRegistry, nodeData) {
14
- const formatted = [];
15
- const seen = new Set();
16
- // Group errors by instancePath
17
- const byPath = new Map();
18
- for (const err of errors) {
19
- const path = err.instancePath || 'root';
20
- if (!byPath.has(path)) {
21
- byPath.set(path, []);
22
- }
23
- byPath.get(path).push(err);
24
- }
25
- for (const [path, pathErrors] of byPath) {
26
- // Check if this is a oneOf failure at root - extract valid types from schema
27
- const isRootOneOf = path === 'root' || path === '/type';
28
- let hasOneOfContext = false;
29
- if (isRootOneOf && pathErrors.length > 1) {
30
- hasOneOfContext = Array.isArray(schema.oneOf);
31
- if (hasOneOfContext) {
32
- const entities = extractEntityInfo(schema, schemaRefRegistry);
33
- const validTypes = entities.map((e) => e.type).sort();
34
- if (validTypes.length > 0) {
35
- const actualValue = nodeData.type;
36
- // Only show type error if the actual type is NOT in the valid types list
37
- if (actualValue !== undefined && !validTypes.includes(String(actualValue))) {
38
- const message = `Invalid type "${actualValue}". Valid types are: ${validTypes.join(', ')}`;
39
- const key = `type:${validTypes.join(',')}`;
40
- if (!seen.has(key)) {
41
- seen.add(key);
42
- formatted.push({ message, dedupeKey: key });
43
- }
44
- }
45
- }
46
- }
47
- }
48
- // Handle individual errors
49
- for (const err of pathErrors) {
50
- // Skip individual type const/enum errors when in oneOf context (we handle it above)
51
- if (hasOneOfContext &&
52
- (err.keyword === 'const' || err.keyword === 'enum') &&
53
- (err.instancePath === '' || err.instancePath === '/type')) {
54
- continue;
55
- }
56
- const parts = err.instancePath.split('/').filter(Boolean);
57
- const fieldName = parts.length > 0 ? parts[parts.length - 1] : 'root';
58
- let message = err.message;
59
- let key = `${err.instancePath}:${err.keyword}`;
60
- // Enhance const errors
61
- if (err.keyword === 'const' && err.params?.allowedValue !== undefined) {
62
- const actual = err.data !== undefined ? `"${err.data}"` : 'missing value';
63
- const expected = `"${err.params.allowedValue}"`;
64
- message = `${fieldName}: expected ${expected}, got ${actual}`;
65
- key = `${err.instancePath}:const:${err.params.allowedValue}`;
66
- }
67
- // Enhance enum errors
68
- else if (err.keyword === 'enum' && err.params?.allowedValues && Array.isArray(err.params.allowedValues)) {
69
- let actual = err.data !== undefined ? `"${err.data}"` : null;
70
- // If err.data is undefined, try to get the value from nodeData using the field name
71
- if (actual === null && fieldName !== 'root') {
72
- const actualValue = nodeData[fieldName];
73
- if (actualValue !== undefined) {
74
- actual = `"${actualValue}"`;
75
- }
76
- }
77
- if (actual === null) {
78
- actual = 'missing value';
79
- }
80
- const allowed = err.params.allowedValues.map((v) => `"${v}"`).join(', ');
81
- message = `${fieldName}: ${actual} is not valid. Allowed: ${allowed}`;
82
- key = `${err.instancePath}:enum:${err.params.allowedValues.join(',')}`;
83
- }
84
- // Generic message with path
85
- else {
86
- message = `${fieldName}: ${err.message}`;
87
- }
88
- if (!seen.has(key)) {
89
- seen.add(key);
90
- formatted.push({ message, dedupeKey: key });
91
- }
92
- }
93
- }
94
- return formatted;
95
- }
4
+ import { bundledSchemasDir } from '../schema/schema';
5
+ import { formatErrors, validateSpace } from '../validate';
96
6
  export async function validate(context, options = {}) {
97
- const { schema, schemaRefRegistry, schemaValidator } = context;
98
- const metadata = schema.metadata;
99
- const readResult = await readSpace(context);
100
- const { nodes, parseIssues } = readResult;
101
- const result = {
102
- validCount: 0,
103
- nodeErrorCount: 0,
104
- nodeErrors: [],
105
- refErrors: [],
106
- duplicateErrors: [],
107
- ruleViolations: [],
108
- hierarchyViolations: [],
109
- orphans: [],
110
- parseIssues,
111
- };
112
- for (const node of nodes) {
113
- const valid = schemaValidator(node.schemaData);
114
- if (valid) {
115
- result.validCount++;
116
- }
117
- else {
118
- result.nodeErrorCount++;
119
- result.nodeErrors.push({
120
- file: node.label,
121
- errors: schemaValidator.errors || [],
122
- nodeData: node.schemaData,
123
- });
124
- }
125
- }
126
- // Detect duplicate node keys (titles)
127
- const titleToFiles = new Map();
128
- for (const node of nodes) {
129
- const title = node.title;
130
- if (!titleToFiles.has(title)) {
131
- titleToFiles.set(title, []);
132
- }
133
- titleToFiles.get(title).push(node.label);
134
- }
135
- for (const [title, files] of titleToFiles) {
136
- if (files.length > 1) {
137
- result.duplicateErrors.push({ title, files });
138
- }
139
- }
140
- // Validate all hierarchy constraints (field references and structure)
141
- const hierarchyValidation = validateGraph(nodes, metadata, readResult.unresolvedRefs);
142
- result.refErrors.push(...hierarchyValidation.refErrors);
143
- result.hierarchyViolations = [...hierarchyValidation.violations];
144
- // Calculate orphan count (informational, not a validation error)
145
- if (metadata.hierarchy) {
146
- result.orphans = [...buildSpaceGraph(nodes, metadata.hierarchy.levels).orphans];
147
- }
148
- // Load and execute rules validation if schema defines rules
149
- if (metadata.rules) {
150
- result.ruleViolations = await validateRules(nodes, metadata.rules);
151
- }
7
+ const { schema, schemaRefRegistry } = context;
8
+ const result = await validateSpace(context);
152
9
  // JSON output mode
153
10
  if (options.json) {
154
11
  const errorsByFile = {};
@@ -193,7 +50,7 @@ export async function validate(context, options = {}) {
193
50
  }, null, 2));
194
51
  return errorCount > 0 ? 1 : 0;
195
52
  }
196
- // Report
53
+ // Human-readable output
197
54
  const reset = '\x1b[0m';
198
55
  const green = '\x1b[32m';
199
56
  const red = '\x1b[31m';
@@ -209,7 +66,6 @@ export async function validate(context, options = {}) {
209
66
  color = colorFor(count, isWarning);
210
67
  }
211
68
  else {
212
- // For non-error items (like "Valid"), green if count > 0, red if 0
213
69
  color = count > 0 ? green : red;
214
70
  }
215
71
  const countStr = String(count).padStart(3);
@@ -266,15 +122,12 @@ export async function validate(context, options = {}) {
266
122
  }
267
123
  if (result.ruleViolations.length > 0) {
268
124
  console.log(`\nRule violations:`);
269
- // Group by category
270
125
  const byCategory = new Map();
271
126
  for (const v of result.ruleViolations) {
272
- if (!byCategory.has(v.category)) {
127
+ if (!byCategory.has(v.category))
273
128
  byCategory.set(v.category, []);
274
- }
275
129
  byCategory.get(v.category).push(v);
276
130
  }
277
- // Report each category
278
131
  for (const [category, violations] of byCategory) {
279
132
  console.log(` ${category.toUpperCase()} (${violations.length}):`);
280
133
  for (const v of violations) {
@@ -289,7 +142,6 @@ export async function validate(context, options = {}) {
289
142
  }
290
143
  }
291
144
  console.log(`\n`);
292
- // Return exit code (0 for success, 1 for validation failures)
293
145
  if (result.nodeErrorCount > 0 ||
294
146
  result.refErrors.length > 0 ||
295
147
  result.duplicateErrors.length > 0 ||
@@ -314,7 +166,6 @@ export async function watchValidate(context) {
314
166
  console.log(` Schema dirs: ${schemaDirs.join(', ')}`);
315
167
  console.log(` Space: ${spacePath}`);
316
168
  console.log(` Press Ctrl+C to stop\n`);
317
- // Save cursor position after header (for clearing later)
318
169
  process.stdout.write('\x1b[s');
319
170
  let exitCode = 0;
320
171
  const innerValidate = async () => {
@@ -336,7 +187,6 @@ export async function watchValidate(context) {
336
187
  },
337
188
  });
338
189
  const handleFileChange = async (filePath, action) => {
339
- // Restore cursor to header position and clear everything below
340
190
  process.stdout.write('\x1b[u\x1b[0J');
341
191
  console.log(`🔄 ${filePath} ${action}, re-validating...\n`);
342
192
  await innerValidate();
@@ -347,7 +197,6 @@ export async function watchValidate(context) {
347
197
  watcher.on('error', (error) => {
348
198
  console.error(`Watcher error: ${error}`);
349
199
  });
350
- // Keep process alive
351
200
  return new Promise((_, reject) => {
352
201
  process.on('SIGINT', () => {
353
202
  console.log('\n\n👋 Stopping watch mode...');
package/dist/config.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { dirname, isAbsolute, join, resolve } from 'node:path';
4
4
  import Ajv from 'ajv';
5
- import { JSON5 } from 'bun';
5
+ import JSON5 from 'json5';
6
6
  import { ENV_CONFIG_VAR, XDG_CONFIG_DIR } from './constants';
7
7
  import { normalizePluginName } from './plugins/util';
8
8
  import { bundledSchemasDir } from './schema/schema';
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { show } from './commands/show';
11
11
  import { listSpaces } from './commands/spaces';
12
12
  import { templateSync } from './commands/template-sync';
13
13
  import { validate, watchValidate } from './commands/validate';
14
- import { validateFile } from './commands/validate-file';
14
+ import { validateFileCommand } from './commands/validate-file';
15
15
  import { getSpaceConfigDir, loadConfig, resolveSchema, setConfigPath } from './config';
16
16
  import { CLI_NAME } from './constants';
17
17
  import { miroSync } from './integrations/miro/sync';
@@ -53,7 +53,7 @@ program
53
53
  .argument('<path>', 'Path to the file to validate')
54
54
  .option('--json', 'Output results as JSON (machine-readable, for hooks)')
55
55
  .action(async (filePath, options) => {
56
- const exitCode = await validateFile(filePath, { json: options.json });
56
+ const exitCode = await validateFileCommand(filePath, { json: options.json });
57
57
  process.exit(exitCode);
58
58
  });
59
59
  program
@@ -1,48 +1,58 @@
1
+ import type { FromSchema } from 'json-schema-to-ts';
1
2
  import type { StructuredContextPlugin } from '../util';
2
- export type TypeInferenceConfig = {
3
- mode?: 'folder-name' | 'off';
4
- folderMap?: Record<string, string>;
5
- };
6
- export type MarkdownPluginConfig = {
7
- templateDir?: string;
8
- fieldMap?: Record<string, string>;
9
- templatePrefix?: string;
10
- typeInference?: TypeInferenceConfig;
3
+ declare const TYPE_INFERENCE_CONFIG_SCHEMA: {
4
+ readonly type: "object";
5
+ readonly properties: {
6
+ readonly mode: {
7
+ readonly type: "string";
8
+ readonly enum: readonly ["folder-name", "off"];
9
+ };
10
+ readonly folderMap: {
11
+ readonly type: "object";
12
+ readonly additionalProperties: {
13
+ readonly type: "string";
14
+ };
15
+ };
16
+ };
17
+ readonly additionalProperties: false;
11
18
  };
12
19
  export declare const MARKDOWN_CONFIG_SCHEMA: {
13
- type: string;
14
- properties: {
15
- templateDir: {
16
- type: string;
17
- format: string;
20
+ readonly type: "object";
21
+ readonly properties: {
22
+ readonly templateDir: {
23
+ readonly type: "string";
24
+ readonly format: "path";
18
25
  };
19
- fieldMap: {
20
- type: string;
21
- additionalProperties: {
22
- type: string;
26
+ readonly fieldMap: {
27
+ readonly type: "object";
28
+ readonly additionalProperties: {
29
+ readonly type: "string";
23
30
  };
24
31
  };
25
- templatePrefix: {
26
- type: string;
32
+ readonly templatePrefix: {
33
+ readonly type: "string";
27
34
  };
28
- typeInference: {
29
- type: string;
30
- properties: {
31
- mode: {
32
- type: string;
33
- enum: string[];
35
+ readonly typeInference: {
36
+ readonly type: "object";
37
+ readonly properties: {
38
+ readonly mode: {
39
+ readonly type: "string";
40
+ readonly enum: readonly ["folder-name", "off"];
34
41
  };
35
- folderMap: {
36
- type: string;
37
- additionalProperties: {
38
- type: string;
42
+ readonly folderMap: {
43
+ readonly type: "object";
44
+ readonly additionalProperties: {
45
+ readonly type: "string";
39
46
  };
40
47
  };
41
48
  };
42
- additionalProperties: boolean;
49
+ readonly additionalProperties: false;
43
50
  };
44
51
  };
45
- additionalProperties: boolean;
52
+ readonly additionalProperties: false;
46
53
  };
54
+ export type MarkdownPluginConfig = FromSchema<typeof MARKDOWN_CONFIG_SCHEMA>;
55
+ export type TypeInferenceConfig = FromSchema<typeof TYPE_INFERENCE_CONFIG_SCHEMA>;
47
56
  export declare function getMarkdownConfig(plugins?: Record<string, Record<string, unknown>>): MarkdownPluginConfig;
48
57
  export declare const markdownPlugin: StructuredContextPlugin;
58
+ export {};
@@ -4,20 +4,21 @@ import { readSpaceDirectory, readSpaceOnAPage } from './read-space';
4
4
  import { renderBullets } from './render-bullets';
5
5
  import { renderMermaid } from './render-mermaid';
6
6
  import { templateSync } from './template-sync';
7
+ const TYPE_INFERENCE_CONFIG_SCHEMA = {
8
+ type: 'object',
9
+ properties: {
10
+ mode: { type: 'string', enum: ['folder-name', 'off'] },
11
+ folderMap: { type: 'object', additionalProperties: { type: 'string' } },
12
+ },
13
+ additionalProperties: false,
14
+ };
7
15
  export const MARKDOWN_CONFIG_SCHEMA = {
8
16
  type: 'object',
9
17
  properties: {
10
18
  templateDir: { type: 'string', format: 'path' }, // format is hint to config loader to resolve relative directories
11
19
  fieldMap: { type: 'object', additionalProperties: { type: 'string' } },
12
20
  templatePrefix: { type: 'string' },
13
- typeInference: {
14
- type: 'object',
15
- properties: {
16
- mode: { type: 'string', enum: ['folder-name', 'off'] },
17
- folderMap: { type: 'object', additionalProperties: { type: 'string' } },
18
- },
19
- additionalProperties: false,
20
- },
21
+ typeInference: TYPE_INFERENCE_CONFIG_SCHEMA,
21
22
  },
22
23
  additionalProperties: false,
23
24
  };
@@ -1,4 +1,4 @@
1
- import type { BaseNode, Relationship, SchemaMetadata } from '../../plugin-api';
1
+ import type { BaseNode, Relationship, SchemaMetadata } from '../../api';
2
2
  /** Type values that identify a space_on_a_page container (not themselves space nodes). */
3
3
  export declare const ON_A_PAGE_TYPES: string[];
4
4
  type StackEntry = {