structured-context 0.10.0 → 0.11.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 (57) hide show
  1. package/README.md +22 -3
  2. package/dist/api.d.ts +21 -3
  3. package/dist/api.js +19 -2
  4. package/dist/commands/diagram.js +1 -1
  5. package/dist/commands/miro-sync.d.ts +3 -0
  6. package/dist/commands/miro-sync.js +8 -0
  7. package/dist/commands/schemas.js +3 -2
  8. package/dist/commands/spaces.js +0 -2
  9. package/dist/commands/validate-file.js +17 -5
  10. package/dist/commands/validate.js +16 -0
  11. package/dist/config.d.ts +8 -12
  12. package/dist/config.js +24 -22
  13. package/dist/index.js +13 -21
  14. package/dist/load-space-graph.d.ts +40 -0
  15. package/dist/load-space-graph.js +42 -0
  16. package/dist/plugins/index.js +3 -1
  17. package/dist/plugins/loader.js +2 -2
  18. package/dist/plugins/markdown/extract-content-links.d.ts +23 -0
  19. package/dist/plugins/markdown/extract-content-links.js +144 -0
  20. package/dist/plugins/markdown/index.js +1 -7
  21. package/dist/plugins/markdown/parse-embedded.js +9 -0
  22. package/dist/plugins/markdown/read-space.js +3 -0
  23. package/dist/plugins/mermaid/index.d.ts +2 -0
  24. package/dist/plugins/mermaid/index.js +14 -0
  25. package/dist/plugins/{markdown → mermaid}/render-mermaid.js +17 -16
  26. package/dist/{integrations → plugins}/miro/cache.d.ts +1 -1
  27. package/dist/plugins/miro/index.d.ts +2 -0
  28. package/dist/plugins/miro/index.js +27 -0
  29. package/dist/{integrations → plugins}/miro/layout.d.ts +2 -2
  30. package/dist/{integrations → plugins}/miro/layout.js +3 -16
  31. package/dist/{integrations → plugins}/miro/styles.d.ts +2 -2
  32. package/dist/plugins/miro/sync.d.ts +7 -0
  33. package/dist/{integrations → plugins}/miro/sync.js +23 -41
  34. package/dist/plugins/util.d.ts +9 -0
  35. package/dist/plugins/util.js +4 -0
  36. package/dist/read/resolve-graph-edges.js +32 -0
  37. package/dist/render/render.d.ts +1 -0
  38. package/dist/render/render.js +20 -16
  39. package/dist/schema/evaluate-rule.js +1 -0
  40. package/dist/schema/schema.d.ts +3 -1
  41. package/dist/schema/schema.js +6 -2
  42. package/dist/schemas/_knowledge_wiki.json +1 -1
  43. package/dist/space-context.d.ts +24 -0
  44. package/dist/space-context.js +41 -0
  45. package/dist/types.d.ts +52 -0
  46. package/dist/validate.d.ts +12 -3
  47. package/dist/validate.js +37 -12
  48. package/docs/concepts.md +23 -0
  49. package/docs/config.md +2 -0
  50. package/package.json +1 -1
  51. package/schemas/_knowledge_wiki.json +1 -1
  52. package/dist/integrations/miro/sync.d.ts +0 -8
  53. /package/dist/plugins/{markdown → mermaid}/render-mermaid.d.ts +0 -0
  54. /package/dist/{integrations → plugins}/miro/cache.js +0 -0
  55. /package/dist/{integrations → plugins}/miro/client.d.ts +0 -0
  56. /package/dist/{integrations → plugins}/miro/client.js +0 -0
  57. /package/dist/{integrations → plugins}/miro/styles.js +0 -0
@@ -1,7 +1,7 @@
1
- import { filterNodes } from '../filter/filter-nodes';
1
+ import { shortenPluginName } from '@/plugins/util';
2
+ import { updateSpaceField } from '../config';
3
+ import { assembleSpaceGraph } from '../load-space-graph';
2
4
  import { loadPlugins } from '../plugins/loader';
3
- import { readSpace } from '../read/read-space';
4
- import { buildSpaceGraph } from '../space-graph';
5
5
  import { buildFormatRegistry } from './registry';
6
6
  export async function executeRender(formatName, context, options) {
7
7
  const pluginMap = context.space?.plugins ?? {};
@@ -12,17 +12,21 @@ export async function executeRender(formatName, context, options) {
12
12
  const available = registry.map((r) => r.qualifiedName).join(', ');
13
13
  throw new Error(`Unknown render format: "${formatName}".${available ? ` Available: ${available}` : ' No formats registered.'}`);
14
14
  }
15
- const { nodes: allNodes } = await readSpace(context);
16
- // Validate: drop nodes that fail schema validation
17
- const { schemaValidator } = context;
18
- const validNodes = allNodes.filter((node) => schemaValidator(node.schemaData));
19
- const levels = context.schema.metadata.hierarchy?.levels ?? [];
20
- let graph = buildSpaceGraph(validNodes, levels);
21
- // Filter: apply filter expression if provided
22
- if (options.filter) {
23
- const expression = context.space.views?.[options.filter]?.expression ?? options.filter;
24
- graph = await filterNodes(expression, graph);
25
- }
26
- const pluginContext = { ...context, pluginConfig: entry.plugin.pluginConfig };
27
- return entry.plugin.plugin.render.render(pluginContext, graph, { format: entry.format.name });
15
+ const graph = await assembleSpaceGraph(context, { filter: options.filter });
16
+ const shortName = shortenPluginName(entry.plugin.plugin.name);
17
+ const pluginContext = {
18
+ ...context,
19
+ pluginConfig: entry.plugin.pluginConfig,
20
+ callbacks: {
21
+ persistConfig: (updates) => {
22
+ for (const [field, value] of Object.entries(updates)) {
23
+ updateSpaceField(context.space.name, field, value, shortName);
24
+ }
25
+ },
26
+ },
27
+ };
28
+ return entry.plugin.plugin.render.render(pluginContext, graph, {
29
+ format: entry.format.name,
30
+ data: options.data,
31
+ });
28
32
  }
@@ -52,6 +52,7 @@ function flattenNode(node) {
52
52
  resolvedType: node.resolvedType,
53
53
  resolvedParentTitle: node.resolvedParents[0]?.title, // first parent or undefined, provided for convenience
54
54
  resolvedParentTitles: node.resolvedParents.map((r) => r.title), // full array of parent titles
55
+ resolvedLinks: node.resolvedLinks,
55
56
  };
56
57
  }
57
58
  /**
@@ -1,6 +1,8 @@
1
1
  import { type AnySchemaObject, type ValidateFunction } from 'ajv';
2
2
  import type { SchemaMetadata, SchemaWithMetadata } from '../types';
3
- export declare const bundledSchemasDir: string;
3
+ export declare let bundledSchemasDir: string;
4
+ /** Override the bundled schemas directory (e.g. when running in a bundled context). */
5
+ export declare function setBundledSchemasDir(dir: string): void;
4
6
  export declare function readRawSchema(schemaPath: string): AnySchemaObject;
5
7
  /**
6
8
  * Build the full two-layer registry for a schema path:
@@ -6,8 +6,12 @@ import Ajv, {} from 'ajv';
6
6
  import JSON5 from 'json5';
7
7
  import { DIALECT_META_SCHEMA, METADATA_SCHEMA, SCHEMA_META_ID, } from './metadata-contract';
8
8
  import { isObject, mergeVariantProperties, resolveJsonPointer } from './schema-refs';
9
- const packageDir = dirname(fileURLToPath(import.meta.url));
10
- export const bundledSchemasDir = join(packageDir, '..', '..', 'schemas');
9
+ const packageDir = import.meta.url ? dirname(fileURLToPath(import.meta.url)) : '';
10
+ export let bundledSchemasDir = packageDir ? join(packageDir, '..', '..', 'schemas') : '';
11
+ /** Override the bundled schemas directory (e.g. when running in a bundled context). */
12
+ export function setBundledSchemasDir(dir) {
13
+ bundledSchemasDir = dir;
14
+ }
11
15
  const validateMetadataContract = new Ajv().compile(METADATA_SCHEMA);
12
16
  export function readRawSchema(schemaPath) {
13
17
  return JSON5.parse(readFileSync(resolve(schemaPath), 'utf-8'));
@@ -207,7 +207,7 @@
207
207
  },
208
208
  "note": {
209
209
  "type": "object",
210
- "description": "Personal notes, journal entries, appointments, experiential records. Your own data about your journey — not sourced from external material.",
210
+ "description": "Personal notes, journal entries, talk notes, or rough thoughts not drawn from an external URL.",
211
211
  "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
212
212
  "properties": {
213
213
  "type": { "const": "note" },
@@ -0,0 +1,24 @@
1
+ import { type Config } from './config';
2
+ import type { SpaceContext } from './types';
3
+ /** Thrown by createSpaceContext when no space matches the given name. */
4
+ export declare class SpaceNotFoundError extends Error {
5
+ readonly spaceName: string;
6
+ constructor(spaceName: string);
7
+ }
8
+ export interface CreateSpaceContextOptions {
9
+ /**
10
+ * Directory to anchor relative plugin-config paths against. Defaults to the source
11
+ * directory recorded by loadConfig (space.sourceDir). Required for a hand-assembled
12
+ * Config, which has no source file — pass e.g. process.cwd(), or the directory the
13
+ * paths are relative to.
14
+ */
15
+ configDir?: string;
16
+ }
17
+ /**
18
+ * Build a SpaceContext for a named space from an already-loaded config.
19
+ *
20
+ * Library-safe: throws {@link SpaceNotFoundError} on an unknown space rather than
21
+ * writing to the console or exiting the process. CLI callers that want the
22
+ * exit-on-error behaviour should wrap this (see buildSpaceContext in index.ts).
23
+ */
24
+ export declare function createSpaceContext(spaceName: string, config: Config, options?: CreateSpaceContextOptions): SpaceContext;
@@ -0,0 +1,41 @@
1
+ import { resolveSchema } from './config';
2
+ import { loadSchema } from './schema/schema';
3
+ /** Thrown by createSpaceContext when no space matches the given name. */
4
+ export class SpaceNotFoundError extends Error {
5
+ spaceName;
6
+ constructor(spaceName) {
7
+ super(`Unknown space "${spaceName}"`);
8
+ this.spaceName = spaceName;
9
+ this.name = 'SpaceNotFoundError';
10
+ }
11
+ }
12
+ /**
13
+ * Build a SpaceContext for a named space from an already-loaded config.
14
+ *
15
+ * Library-safe: throws {@link SpaceNotFoundError} on an unknown space rather than
16
+ * writing to the console or exiting the process. CLI callers that want the
17
+ * exit-on-error behaviour should wrap this (see buildSpaceContext in index.ts).
18
+ */
19
+ export function createSpaceContext(spaceName, config, options = {}) {
20
+ const space = config.spaces.find((s) => s.name === spaceName);
21
+ if (!space) {
22
+ throw new SpaceNotFoundError(spaceName);
23
+ }
24
+ const configDir = options.configDir ?? space.sourceDir;
25
+ if (configDir === undefined) {
26
+ throw new Error(`Cannot resolve a config directory for space "${spaceName}": its Config was not loaded from a ` +
27
+ `file. Pass options.configDir (e.g. process.cwd()) to anchor relative plugin paths, or load ` +
28
+ `the config with loadConfig().`);
29
+ }
30
+ const resolvedSchemaPath = resolveSchema(config, space);
31
+ const { schema, schemaRefRegistry, schemaValidator } = loadSchema(resolvedSchemaPath);
32
+ return {
33
+ space,
34
+ config,
35
+ resolvedSchemaPath,
36
+ schema,
37
+ schemaRefRegistry,
38
+ schemaValidator,
39
+ configDir,
40
+ };
41
+ }
package/dist/types.d.ts CHANGED
@@ -56,6 +56,42 @@ export type UnresolvedRef = {
56
56
  /** Human-readable message matching validate-graph output format. */
57
57
  message: string;
58
58
  };
59
+ /**
60
+ * Shared fields for all content link variants.
61
+ */
62
+ type ContentLinkBase = {
63
+ /** Display text of the link (text content for markdown links, inner text or target for wikilinks). */
64
+ text: string;
65
+ /** Raw link target: URL for markdown links, wikilink path (without [[ ]]) for wikilinks. */
66
+ target: string;
67
+ /** Whether the link navigates to a resource or transcludes/embeds it. */
68
+ action: 'link' | 'embed';
69
+ /** Anchor fragment (heading or block ref, e.g. 'heading-title' or '^blockid') if present. */
70
+ anchor?: string;
71
+ };
72
+ /**
73
+ * A link extracted from node content at parse time, before location classification.
74
+ * Populated by plugins that support content link extraction (e.g. the markdown plugin).
75
+ */
76
+ export type ContentLink = ContentLinkBase & {
77
+ /** Link syntax — used during resolution to determine how to classify the target. */
78
+ linkSyntax: 'wikilink' | 'markdown';
79
+ };
80
+ /**
81
+ * A fully classified link from node content, after location resolution.
82
+ * Present on SpaceNode after graph resolution.
83
+ */
84
+ export type ResolvedContentLink = ContentLinkBase & {
85
+ /**
86
+ * Resolved location classification:
87
+ * - 'node' — wikilink resolved to a known space node
88
+ * - 'internal' — wikilink or relative path, in the source system but not a space node
89
+ * - 'external' — http/https URL
90
+ * - 'system' — file:// URL
91
+ * - 'protocol' — other scheme (e.g. obsidian://)
92
+ */
93
+ location: 'node' | 'internal' | 'external' | 'system' | 'protocol';
94
+ };
59
95
  /**
60
96
  * A node as produced by a parse plugin — raw type from content, no graph resolution applied.
61
97
  * Core enriches this into a SpaceNode after parsing.
@@ -71,6 +107,11 @@ export type BaseNode = {
71
107
  linkTargets: string[];
72
108
  /** Raw type string from content, as written by the user. */
73
109
  type: string;
110
+ /**
111
+ * Links extracted from content body and non-edge string fields at parse time.
112
+ * Populated by plugins that support content link extraction; absent if not populated.
113
+ */
114
+ contentLinks?: ContentLink[];
74
115
  };
75
116
  /**
76
117
  * A fully resolved node — enriched by core after plugin parsing.
@@ -85,6 +126,11 @@ export type SpaceNode = BaseNode & {
85
126
  * Always present, empty if no parents resolved.
86
127
  */
87
128
  resolvedParents: ResolvedParentRef[];
129
+ /**
130
+ * Links from content body and non-edge fields, with location classified after graph resolution.
131
+ * Always present, empty if the plugin did not populate rawContentLinks.
132
+ */
133
+ resolvedLinks: ResolvedContentLink[];
88
134
  };
89
135
  export type ParseIssue = {
90
136
  /** Source identifier of the file or item (filename or heading title). */
@@ -165,6 +211,12 @@ export interface FileValidationResult {
165
211
  message: string;
166
212
  }>;
167
213
  errorCount: number;
214
+ /** Warnings keyed by composite id. Do not affect exit code or errorCount. */
215
+ warnings: Record<string, {
216
+ kind: string;
217
+ message: string;
218
+ }>;
219
+ warningCount: number;
168
220
  inSpace: true;
169
221
  }
170
222
  export interface FileNotInSpaceResult {
@@ -1,7 +1,7 @@
1
1
  import type { ErrorObject } from 'ajv';
2
2
  import type { Config } from './config';
3
3
  import { extractEntityInfo } from './schema/schema';
4
- import type { GraphViolation, ParseIssue, RuleViolation, SchemaWithMetadata, SpaceContext, SpaceNode, ValidateFileOutput } from './types';
4
+ import type { GraphViolation, ParseIssue, ReadSpaceResult, RuleViolation, SchemaWithMetadata, SpaceContext, SpaceNode, ValidateFileOutput } from './types';
5
5
  export interface FormattedError {
6
6
  message: string;
7
7
  dedupeKey: string;
@@ -27,6 +27,11 @@ export interface ValidationResult {
27
27
  hierarchyViolations: GraphViolation[];
28
28
  orphans: SpaceNode[];
29
29
  parseIssues: ParseIssue[];
30
+ /** Content links that could not be resolved to a space node. Warning only — not counted in errors. */
31
+ unresolvedContentLinks: Array<{
32
+ file: string;
33
+ target: string;
34
+ }>;
30
35
  }
31
36
  /**
32
37
  * Format AJV errors for better readability.
@@ -37,7 +42,9 @@ export declare function formatErrors(errors: ErrorObject[], schema: SchemaWithMe
37
42
  * Run full validation on a space and return a structured result.
38
43
  * Does not output to console or call process.exit.
39
44
  */
40
- export declare function validateSpace(context: SpaceContext): Promise<ValidationResult>;
45
+ export declare function validateSpace(context: SpaceContext, options?: {
46
+ readResult?: ReadSpaceResult;
47
+ }): Promise<ValidationResult>;
41
48
  /**
42
49
  * Validate a single file within its space.
43
50
  *
@@ -47,4 +54,6 @@ export declare function validateSpace(context: SpaceContext): Promise<Validation
47
54
  * Returns a result object — does not output to console or call process.exit.
48
55
  * If the file is not in any configured space, returns a result with `inSpace: false`.
49
56
  */
50
- export declare function validateFile(filePath: string, config: Config): Promise<ValidateFileOutput>;
57
+ export declare function validateFile(filePath: string, config: Config, options?: {
58
+ configDir?: string;
59
+ }): Promise<ValidateFileOutput>;
package/dist/validate.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { isAbsolute, relative, resolve } from 'node:path';
2
- import { getSpaceConfigDir, resolveSchema } from './config';
3
2
  import { readSpace } from './read/read-space';
4
- import { extractEntityInfo, loadSchema } from './schema/schema';
3
+ import { extractEntityInfo } from './schema/schema';
5
4
  import { validateGraph } from './schema/validate-graph';
6
5
  import { validateRules } from './schema/validate-rules';
6
+ import { createSpaceContext } from './space-context';
7
7
  import { buildSpaceGraph } from './space-graph';
8
8
  /**
9
9
  * Format AJV errors for better readability.
@@ -96,10 +96,10 @@ export function formatErrors(errors, schema, schemaRefRegistry, nodeData) {
96
96
  * Run full validation on a space and return a structured result.
97
97
  * Does not output to console or call process.exit.
98
98
  */
99
- export async function validateSpace(context) {
99
+ export async function validateSpace(context, options = {}) {
100
100
  const { schema, schemaRefRegistry, schemaValidator } = context;
101
101
  const metadata = schema.metadata;
102
- const readResult = await readSpace(context);
102
+ const readResult = options.readResult ?? (await readSpace(context));
103
103
  const { nodes, parseIssues } = readResult;
104
104
  // Pre-extract valid types for early type validation
105
105
  const validTypes = Array.isArray(schema.oneOf) ? extractEntityInfo(schema, schemaRefRegistry).map((e) => e.type) : [];
@@ -113,6 +113,7 @@ export async function validateSpace(context) {
113
113
  hierarchyViolations: [],
114
114
  orphans: [],
115
115
  parseIssues,
116
+ unresolvedContentLinks: [],
116
117
  };
117
118
  for (const node of nodes) {
118
119
  // Early type validation - check before full AJV validation to prevent cascading errors
@@ -174,6 +175,19 @@ export async function validateSpace(context) {
174
175
  if (metadata.rules) {
175
176
  result.ruleViolations = await validateRules(nodes, metadata.rules);
176
177
  }
178
+ // Collect unresolved content links (warnings — not errors)
179
+ const seen = new Set();
180
+ for (const node of nodes) {
181
+ for (const link of node.resolvedLinks) {
182
+ if (link.location === 'internal') {
183
+ const key = `${node.label}::${link.target}`;
184
+ if (!seen.has(key)) {
185
+ seen.add(key);
186
+ result.unresolvedContentLinks.push({ file: node.label, target: link.target });
187
+ }
188
+ }
189
+ }
190
+ }
177
191
  return result;
178
192
  }
179
193
  /** Find which space a file belongs to by checking directory containment. */
@@ -189,12 +203,6 @@ function resolveFileSpace(filePath, config) {
189
203
  }
190
204
  return null;
191
205
  }
192
- function buildContextForSpace(space, config) {
193
- const resolvedSchemaPath = resolveSchema(config, space);
194
- const { schema, schemaRefRegistry, schemaValidator } = loadSchema(resolvedSchemaPath);
195
- const configDir = getSpaceConfigDir(space.name);
196
- return { space, config, resolvedSchemaPath, schema, schemaRefRegistry, schemaValidator, configDir };
197
- }
198
206
  /**
199
207
  * Validate a single file within its space.
200
208
  *
@@ -204,7 +212,7 @@ function buildContextForSpace(space, config) {
204
212
  * Returns a result object — does not output to console or call process.exit.
205
213
  * If the file is not in any configured space, returns a result with `inSpace: false`.
206
214
  */
207
- export async function validateFile(filePath, config) {
215
+ export async function validateFile(filePath, config, options = {}) {
208
216
  const resolved = resolveFileSpace(filePath, config);
209
217
  if (!resolved) {
210
218
  return {
@@ -214,7 +222,7 @@ export async function validateFile(filePath, config) {
214
222
  };
215
223
  }
216
224
  const { space, label } = resolved;
217
- const context = buildContextForSpace(space, config);
225
+ const context = createSpaceContext(space.name, config, { configDir: options.configDir });
218
226
  const { schema, schemaRefRegistry, schemaValidator } = context;
219
227
  const metadata = schema.metadata;
220
228
  const readResult = await readSpace(context);
@@ -280,12 +288,29 @@ export async function validateFile(filePath, config) {
280
288
  }
281
289
  }
282
290
  }
291
+ // Unresolved content links for this node (warnings — do not affect errorCount)
292
+ const warnings = {};
293
+ const targetNode = nodes.find((n) => n.label === label);
294
+ if (targetNode) {
295
+ const seen = new Set();
296
+ for (const link of targetNode.resolvedLinks) {
297
+ if (link.location === 'internal' && !seen.has(link.target)) {
298
+ seen.add(link.target);
299
+ warnings[`content-link:${link.target}`] = {
300
+ kind: 'content-link',
301
+ message: `Unresolved content link: ${link.target}`,
302
+ };
303
+ }
304
+ }
305
+ }
283
306
  return {
284
307
  file: isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath),
285
308
  label,
286
309
  space: space.name,
287
310
  errors,
288
311
  errorCount: Object.keys(errors).length,
312
+ warnings,
313
+ warningCount: Object.keys(warnings).length,
289
314
  inSpace: true,
290
315
  };
291
316
  }
package/docs/concepts.md CHANGED
@@ -254,6 +254,29 @@ Each entry is a `ResolvedParentRef` object:
254
254
 
255
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
256
 
257
+ ### Content links
258
+
259
+ **Content links** (`resolvedLinks` on `SpaceNode`) are all links extracted from a node's content at parse time — both from frontmatter non-edge fields and from the body text. They are distinct from graph edges: they are never used to infer structure, but are available to consumers for rendering, auditing, or navigation.
260
+
261
+ Each entry is a `ResolvedContentLink` object:
262
+
263
+ | Field | Type | Description |
264
+ |---|---|---|
265
+ | `text` | `string` | Display text of the link |
266
+ | `target` | `string` | Raw link target (URL or wikilink path) |
267
+ | `action` | `'link' \| 'embed'` | Whether the link navigates or transcludes |
268
+ | `anchor` | `string?` | Fragment component (heading or block ref) if present |
269
+ | `location` | `'node' \| 'internal' \| 'external' \| 'system' \| 'protocol'` | Resolved location classification |
270
+
271
+ **Location classification:**
272
+ - `node` — wikilink resolved to a known space node
273
+ - `internal` — wikilink or relative path to an in-source target that is not a space node
274
+ - `external` — `http://` or `https://` URL
275
+ - `system` — `file://` URL
276
+ - `protocol` — any other scheme (e.g. `obsidian://`)
277
+
278
+ **Sources:** Both wikilink syntax (`[[Target]]`, `![[Image]]`) and markdown link syntax (`[text](url)`, `![alt](url)`) are extracted. Bare URLs in frontmatter string fields are also captured. Graph edge fields (hierarchy `parent`, relationship fields) are excluded — those are graph edges, not content links.
279
+
257
280
  ### Anchor
258
281
 
259
282
  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:
package/docs/config.md CHANGED
@@ -28,6 +28,8 @@ A space is a named directory or single file registered in the config. Example:
28
28
 
29
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
30
 
31
+ A config must define at least one of `spaces` or `includeSpacesFrom`. When using `includeSpacesFrom` alone, `spaces` can be omitted entirely.
32
+
31
33
  ## Plugins
32
34
 
33
35
  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):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "structured-context",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Keep your content clean and your thinking clear with assistive tools for structured knowledge",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -207,7 +207,7 @@
207
207
  },
208
208
  "note": {
209
209
  "type": "object",
210
- "description": "Personal notes, journal entries, appointments, experiential records. Your own data about your journey — not sourced from external material.",
210
+ "description": "Personal notes, journal entries, talk notes, or rough thoughts not drawn from an external URL.",
211
211
  "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
212
212
  "properties": {
213
213
  "type": { "const": "note" },
@@ -1,8 +0,0 @@
1
- import type { SpaceContext } from '../../types';
2
- interface SyncOptions {
3
- newFrame?: string;
4
- dryRun?: boolean;
5
- verbose?: boolean;
6
- }
7
- export declare function miroSync(context: SpaceContext, options: SyncOptions): Promise<void>;
8
- export {};
File without changes
File without changes
File without changes
File without changes