structured-context 0.11.0 → 0.13.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 (138) hide show
  1. package/README.md +52 -198
  2. package/dist/api.d.ts +17 -15
  3. package/dist/api.js +12 -9
  4. package/dist/commands/diagram.d.ts +1 -0
  5. package/dist/commands/diagram.js +7 -1
  6. package/dist/commands/docs.js +1 -0
  7. package/dist/commands/dump.d.ts +15 -2
  8. package/dist/commands/dump.js +18 -4
  9. package/dist/commands/providers.d.ts +4 -0
  10. package/dist/commands/providers.js +52 -0
  11. package/dist/commands/render.d.ts +4 -0
  12. package/dist/commands/render.js +64 -9
  13. package/dist/commands/schemas.d.ts +2 -2
  14. package/dist/commands/schemas.js +133 -22
  15. package/dist/commands/show.d.ts +1 -0
  16. package/dist/commands/show.js +7 -1
  17. package/dist/commands/spaces.d.ts +3 -1
  18. package/dist/commands/spaces.js +11 -2
  19. package/dist/commands/validate-file.d.ts +3 -0
  20. package/dist/commands/validate-file.js +24 -6
  21. package/dist/commands/validate.d.ts +4 -0
  22. package/dist/commands/validate.js +79 -23
  23. package/dist/config.d.ts +25 -1
  24. package/dist/config.js +106 -12
  25. package/dist/filter/augment-nodes.d.ts +3 -1
  26. package/dist/filter/augment-nodes.js +12 -7
  27. package/dist/filter/filter-nodes.js +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +96 -31
  30. package/dist/judge/context.d.ts +20 -0
  31. package/dist/judge/context.js +105 -0
  32. package/dist/judge/factory.d.ts +44 -0
  33. package/dist/judge/factory.js +148 -0
  34. package/dist/judge/index.d.ts +13 -0
  35. package/dist/judge/index.js +8 -0
  36. package/dist/judge/judge-node.d.ts +27 -0
  37. package/dist/judge/judge-node.js +122 -0
  38. package/dist/judge/prompt.d.ts +10 -0
  39. package/dist/judge/prompt.js +92 -0
  40. package/dist/judge/provider/anthropic.d.ts +17 -0
  41. package/dist/judge/provider/anthropic.js +43 -0
  42. package/dist/judge/provider/fake.d.ts +26 -0
  43. package/dist/judge/provider/fake.js +56 -0
  44. package/dist/judge/provider/jev.d.ts +37 -0
  45. package/dist/judge/provider/jev.js +169 -0
  46. package/dist/judge/provider/openai-decisions.d.ts +21 -0
  47. package/dist/judge/provider/openai-decisions.js +84 -0
  48. package/dist/judge/provider/openai.d.ts +20 -0
  49. package/dist/judge/provider/openai.js +53 -0
  50. package/dist/judge/provider/per-rule.d.ts +10 -0
  51. package/dist/judge/provider/per-rule.js +18 -0
  52. package/dist/judge/trim.d.ts +27 -0
  53. package/dist/judge/trim.js +62 -0
  54. package/dist/judge/types.d.ts +92 -0
  55. package/dist/judge/types.js +9 -0
  56. package/dist/judge/verdicts.d.ts +24 -0
  57. package/dist/judge/verdicts.js +82 -0
  58. package/dist/load-space-graph.d.ts +7 -5
  59. package/dist/load-space-graph.js +19 -8
  60. package/dist/plugins/loader.js +3 -3
  61. package/dist/plugins/markdown/extract-content-links.js +8 -29
  62. package/dist/plugins/markdown/index.js +8 -2
  63. package/dist/plugins/markdown/parse-embedded.d.ts +8 -0
  64. package/dist/plugins/markdown/parse-embedded.js +195 -24
  65. package/dist/plugins/markdown/read-space.js +42 -14
  66. package/dist/plugins/markdown/render-directory.d.ts +11 -0
  67. package/dist/plugins/markdown/render-directory.js +130 -0
  68. package/dist/plugins/markdown/template-sync.js +3 -4
  69. package/dist/plugins/markdown/util.d.ts +26 -5
  70. package/dist/plugins/markdown/util.js +56 -15
  71. package/dist/plugins/mermaid/render-mermaid.js +2 -1
  72. package/dist/plugins/miro/sync.js +7 -6
  73. package/dist/plugins/util.d.ts +39 -1
  74. package/dist/plugins/util.js +139 -0
  75. package/dist/read/resolve-graph-edges.d.ts +1 -1
  76. package/dist/read/resolve-graph-edges.js +46 -27
  77. package/dist/read/wikilink-utils.d.ts +17 -0
  78. package/dist/read/wikilink-utils.js +40 -0
  79. package/dist/render/render.d.ts +3 -1
  80. package/dist/render/render.js +2 -2
  81. package/dist/schema/evaluate-rule.d.ts +33 -2
  82. package/dist/schema/evaluate-rule.js +58 -4
  83. package/dist/schema/gating.d.ts +69 -0
  84. package/dist/schema/gating.js +253 -0
  85. package/dist/schema/meta-schemas.d.ts +13 -0
  86. package/dist/schema/meta-schemas.js +50 -0
  87. package/dist/schema/metadata-contract.d.ts +952 -183
  88. package/dist/schema/metadata-contract.js +177 -33
  89. package/dist/schema/rule-filter.d.ts +12 -0
  90. package/dist/schema/rule-filter.js +46 -0
  91. package/dist/schema/rule-severity.d.ts +13 -0
  92. package/dist/schema/rule-severity.js +33 -0
  93. package/dist/schema/schema.d.ts +14 -8
  94. package/dist/schema/schema.js +308 -199
  95. package/dist/schema/validate-graph.d.ts +2 -1
  96. package/dist/schema/validate-graph.js +40 -23
  97. package/dist/schema/validate-rules.d.ts +16 -3
  98. package/dist/schema/validate-rules.js +91 -29
  99. package/dist/schemas/_knowledge_wiki.json +24 -24
  100. package/dist/schemas/_okf.json +172 -0
  101. package/dist/schemas/_ost_strict.json +17 -8
  102. package/dist/schemas/_sctx_base.json +3 -3
  103. package/dist/schemas/_strategy_general.json +38 -38
  104. package/dist/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
  105. package/dist/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
  106. package/dist/schemas/generated/latest/_structured_context_schema_meta.json +400 -0
  107. package/dist/schemas/knowledge_wiki.json +10 -9
  108. package/dist/schemas/okf.json +8 -0
  109. package/dist/schemas/strategy_general.json +15 -14
  110. package/dist/schemas/strict_ost.json +17 -16
  111. package/dist/space-context.d.ts +5 -2
  112. package/dist/space-context.js +9 -4
  113. package/dist/space-graph.d.ts +9 -7
  114. package/dist/space-graph.js +7 -5
  115. package/dist/testing.d.ts +33 -0
  116. package/dist/testing.js +72 -0
  117. package/dist/types.d.ts +64 -16
  118. package/dist/validate.d.ts +62 -9
  119. package/dist/validate.js +229 -114
  120. package/docs/concepts.md +65 -9
  121. package/docs/config.md +78 -1
  122. package/docs/rules.md +168 -50
  123. package/docs/schemas.md +152 -52
  124. package/package.json +17 -11
  125. package/schemas/_knowledge_wiki.json +24 -24
  126. package/schemas/_okf.json +172 -0
  127. package/schemas/_ost_strict.json +17 -8
  128. package/schemas/_sctx_base.json +3 -3
  129. package/schemas/_strategy_general.json +38 -38
  130. package/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
  131. package/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
  132. package/schemas/generated/latest/_structured_context_schema_meta.json +400 -0
  133. package/schemas/knowledge_wiki.json +10 -9
  134. package/schemas/okf.json +8 -0
  135. package/schemas/strategy_general.json +15 -14
  136. package/schemas/strict_ost.json +17 -16
  137. package/dist/schemas/generated/_structured_context_schema_meta.json +0 -191
  138. package/schemas/generated/_structured_context_schema_meta.json +0 -191
@@ -1,17 +1,72 @@
1
- import { writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
2
3
  import { discoverPlugins, loadPlugins } from '../plugins/loader';
4
+ import { isRenderMultiFileOutput } from '../plugins/util';
3
5
  import { buildFormatRegistry } from '../render/registry';
4
6
  import { executeRender } from '../render/render';
5
7
  export async function render(context, format, options) {
6
- const result = await executeRender(format, context, { filter: options.filter });
7
- if (options.output) {
8
- writeFileSync(options.output, result);
9
- console.error(`Written to ${options.output}`);
8
+ const result = await executeRender(format, context, {
9
+ filter: options.filter,
10
+ hierarchy: options.hierarchy,
11
+ data: options.data,
12
+ });
13
+ const targetPath = options.out ?? options.output;
14
+ const force = Boolean(options.force);
15
+ if (typeof result === 'string') {
16
+ if (targetPath) {
17
+ const resolved = resolve(targetPath);
18
+ if (existsSync(resolved)) {
19
+ if (statSync(resolved).isDirectory()) {
20
+ throw new Error(`Output path "${targetPath}" is a directory. Please specify a file path.`);
21
+ }
22
+ if (!force) {
23
+ throw new Error(`File "${targetPath}" already exists. Use --force to overwrite.`);
24
+ }
25
+ }
26
+ mkdirSync(dirname(resolved), { recursive: true });
27
+ writeFileSync(resolved, result, 'utf8');
28
+ console.error(`Written to ${targetPath}`);
29
+ }
30
+ else {
31
+ process.stdout.write(result);
32
+ if (!result.endsWith('\n'))
33
+ process.stdout.write('\n');
34
+ }
35
+ return;
10
36
  }
11
- else {
12
- process.stdout.write(result);
13
- if (!result.endsWith('\n'))
14
- process.stdout.write('\n');
37
+ if (isRenderMultiFileOutput(result)) {
38
+ if (targetPath) {
39
+ const targetDir = resolve(targetPath);
40
+ const conflicts = [];
41
+ for (const file of result.files) {
42
+ const fullPath = join(targetDir, file.path);
43
+ if (existsSync(fullPath)) {
44
+ conflicts.push(file.path);
45
+ }
46
+ }
47
+ if (conflicts.length > 0 && !force) {
48
+ const preview = conflicts
49
+ .slice(0, 5)
50
+ .map((f) => ` ${f}`)
51
+ .join('\n');
52
+ const extra = conflicts.length > 5 ? `\n ... and ${conflicts.length - 5} more` : '';
53
+ throw new Error(`Refusing to overwrite ${conflicts.length} existing file(s) in "${targetPath}" without --force:\n${preview}${extra}`);
54
+ }
55
+ for (const file of result.files) {
56
+ const fullPath = join(targetDir, file.path);
57
+ mkdirSync(dirname(fullPath), { recursive: true });
58
+ writeFileSync(fullPath, file.content, 'utf8');
59
+ }
60
+ console.error(`Written ${result.files.length} file(s) to ${targetPath}`);
61
+ }
62
+ else {
63
+ console.log(`Rendered ${result.files.length} file(s):`);
64
+ for (const file of result.files) {
65
+ const bytes = Buffer.byteLength(file.content, 'utf8');
66
+ console.log(` ${file.path} (${bytes} bytes)`);
67
+ }
68
+ console.log('\nSpecify --out <dir> to write files to disk.');
69
+ }
15
70
  }
16
71
  }
17
72
  export async function renderList(context) {
@@ -1,6 +1,6 @@
1
- export declare function listSchemas(): void;
1
+ export declare function listSchemas(): Promise<void>;
2
2
  export declare function showSchema(file: string | undefined, options: {
3
3
  space?: string;
4
4
  raw: boolean;
5
5
  mermaidErd?: boolean;
6
- }): void;
6
+ }): Promise<void>;
@@ -1,8 +1,13 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { loadConfig, resolveSchema } from '../config';
4
- import { bundledSchemasDir, extractEntityInfo, loadSchema, readRawSchema } from '../schema/schema';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { loadConfig } from '../config';
4
+ import { discoverPlugins } from '../plugins/loader';
5
+ import { normalizePluginName, resolvePluginSchemasDir, resolveSpacePluginSchemas, shortenPluginName, } from '../plugins/util';
6
+ import { ruleSemantic } from '../schema/rule-severity';
7
+ import { bundledSchemasDir, extractEntityInfo, loadSchema, readRawSchema, } from '../schema/schema';
5
8
  import { mergeVariantProperties, resolveRef } from '../schema/schema-refs';
9
+ import { DEFAULT_SEVERITY_BY_CATEGORY } from '../schema/validate-rules';
10
+ import { createSpaceContext } from '../space-context';
6
11
  function isBundledPath(schemaPath) {
7
12
  return dirname(schemaPath) === bundledSchemasDir;
8
13
  }
@@ -73,9 +78,14 @@ function showDefs(defs) {
73
78
  }
74
79
  }
75
80
  function showMetadata(metadata) {
76
- if (metadata.hierarchy?.levels.length) {
77
- const parts = metadata.hierarchy.levels.map((l) => (l.selfRef ? `${l.type}(+)` : l.type));
78
- console.log(`\nhierarchy: ${parts.join(' → ')}`);
81
+ const hierarchies = metadata.hierarchies ?? [];
82
+ if (hierarchies.length > 0) {
83
+ console.log('');
84
+ for (const hierarchy of hierarchies) {
85
+ const parts = hierarchy.levels.map((l) => (l.selfRef ? `${l.type}(+)` : l.type));
86
+ const main = hierarchy === metadata.hierarchy ? ' (main)' : '';
87
+ console.log(`hierarchy "${hierarchy.name}"${main}: ${parts.join(' → ')}`);
88
+ }
79
89
  }
80
90
  else {
81
91
  console.log('\nhierarchy: (none)');
@@ -88,15 +98,15 @@ function showMetadata(metadata) {
88
98
  const groups = new Map();
89
99
  for (const rule of metadata.rules) {
90
100
  const rules = groups.get(rule.category) ?? [];
91
- rules.push({ id: rule.id, description: rule.description });
101
+ rules.push(rule);
92
102
  groups.set(rule.category, rules);
93
103
  }
94
104
  if (groups.size > 0) {
95
105
  console.log('\nRules:');
96
- for (const [group, items] of groups) {
97
- console.log(` ${group}:`);
98
- for (const item of items) {
99
- console.log(` ${item.id}: ${item.description}`);
106
+ for (const [category, rules] of groups) {
107
+ console.log(` ${category} (default severity: ${DEFAULT_SEVERITY_BY_CATEGORY[category]}):`);
108
+ for (const rule of rules) {
109
+ console.log(` ${rule.id}: ${rule.description}${ruleAnnotations(rule)}`);
100
110
  }
101
111
  }
102
112
  }
@@ -110,7 +120,7 @@ function showMetadata(metadata) {
110
120
  }
111
121
  }
112
122
  }
113
- function showRegistry(schemaPath, schemaRefRegistry) {
123
+ function showRegistry(schemaPath, schemaRefRegistry, pluginSchemas) {
114
124
  const bundledIds = new Set();
115
125
  if (existsSync(bundledSchemasDir)) {
116
126
  for (const file of readdirSync(bundledSchemasDir).filter((f) => f.endsWith('.json'))) {
@@ -119,9 +129,22 @@ function showRegistry(schemaPath, schemaRefRegistry) {
119
129
  bundledIds.add(s.$id);
120
130
  }
121
131
  }
132
+ const pluginIds = new Set();
133
+ if (pluginSchemas) {
134
+ for (const { schemasDir } of pluginSchemas) {
135
+ if (!existsSync(schemasDir))
136
+ continue;
137
+ for (const file of readdirSync(schemasDir).filter((f) => f.endsWith('.json'))) {
138
+ const s = readRawSchema(join(schemasDir, file));
139
+ if (typeof s.$id === 'string')
140
+ pluginIds.add(s.$id);
141
+ }
142
+ }
143
+ }
122
144
  console.log(`\nRegistry (${schemaPath}):`);
123
145
  for (const [id] of schemaRefRegistry) {
124
- console.log(` [${bundledIds.has(id) ? 'bundled' : 'local'}] ${id}`);
146
+ const origin = bundledIds.has(id) ? 'bundled' : pluginIds.has(id) ? 'plugin' : 'local';
147
+ console.log(` [${origin}] ${id}`);
125
148
  }
126
149
  }
127
150
  /**
@@ -130,7 +153,6 @@ function showRegistry(schemaPath, schemaRefRegistry) {
130
153
  */
131
154
  function generateMermaidErd(metadata, entities) {
132
155
  let mmd = 'erDiagram\n';
133
- const hierarchyLevels = metadata.hierarchy?.levels ?? [];
134
156
  // Generate entity definitions with properties
135
157
  for (const entity of entities) {
136
158
  const safeName = entity.type.replace(/[^a-zA-Z0-9_]/g, '_');
@@ -145,8 +167,8 @@ function generateMermaidErd(metadata, entities) {
145
167
  }
146
168
  mmd += ' }\n';
147
169
  }
148
- // Generate relationships based on hierarchy metadata
149
- if (hierarchyLevels.length > 0) {
170
+ // Generate relationships based on the levels of each hierarchy
171
+ for (const { levels: hierarchyLevels } of metadata.hierarchies ?? []) {
150
172
  for (let i = 0; i < hierarchyLevels.length - 1; i++) {
151
173
  const current = hierarchyLevels[i];
152
174
  const next = hierarchyLevels[i + 1];
@@ -166,7 +188,26 @@ function generateMermaidErd(metadata, entities) {
166
188
  }
167
189
  return mmd;
168
190
  }
169
- export function listSchemas() {
191
+ /** Bracketed notes on a rule: semantic, severity (and verdict severities) and `when`. Empty for a plain rule. */
192
+ function ruleAnnotations(rule) {
193
+ const notes = [];
194
+ const semantic = ruleSemantic(rule);
195
+ if (rule.global)
196
+ notes.push('global');
197
+ if (semantic)
198
+ notes.push('semantic');
199
+ if (rule.severity)
200
+ notes.push(`severity: ${rule.severity}`);
201
+ if (semantic?.severity) {
202
+ notes.push(`verdict severity: ${Object.entries(semantic.severity)
203
+ .map(([verdict, sev]) => `${verdict}=${sev}`)
204
+ .join(', ')}`);
205
+ }
206
+ if (rule.when)
207
+ notes.push(`when: ${rule.when}`);
208
+ return notes.length > 0 ? ` [${notes.join('] [')}]` : '';
209
+ }
210
+ export async function listSchemas() {
170
211
  const config = loadConfig();
171
212
  // List all schemas known to config
172
213
  if (existsSync(bundledSchemasDir)) {
@@ -182,6 +223,23 @@ export function listSchemas() {
182
223
  }
183
224
  }
184
225
  }
226
+ // List schemas provided by discovered plugins
227
+ const plugins = await discoverPlugins();
228
+ for (const p of plugins) {
229
+ if (p.schemasDir && existsSync(p.schemasDir)) {
230
+ const files = readdirSync(p.schemasDir)
231
+ .filter((f) => f.endsWith('.json'))
232
+ .sort();
233
+ if (files.length > 0) {
234
+ console.log(`\nPlugin schemas (${p.name}):`);
235
+ for (const file of files) {
236
+ const schema = readRawSchema(join(p.schemasDir, file));
237
+ const id = typeof schema.$id === 'string' ? schema.$id : '(no $id)';
238
+ console.log(` ${file}${file.startsWith('_') ? ' [partial]' : ''} (${id})`);
239
+ }
240
+ }
241
+ }
242
+ }
185
243
  const configured = [];
186
244
  const seen = new Set();
187
245
  if (config.schema && !isBundledPath(config.schema)) {
@@ -201,16 +259,23 @@ export function listSchemas() {
201
259
  }
202
260
  }
203
261
  }
204
- export function showSchema(file, options) {
262
+ export async function showSchema(file, options) {
205
263
  const config = loadConfig();
206
264
  let schemaPath;
265
+ let schema;
266
+ let schemaRefRegistry;
267
+ let pluginSchemas;
207
268
  if (options.space) {
208
269
  const space = config.spaces.find((s) => s.name === options.space);
209
270
  if (!space) {
210
271
  console.error(`Error: Unknown space "${options.space}"`);
211
272
  process.exit(1);
212
273
  }
213
- schemaPath = resolveSchema(config, space);
274
+ const context = createSpaceContext(options.space, config);
275
+ schemaPath = context.resolvedSchemaPath;
276
+ schema = context.schema;
277
+ schemaRefRegistry = context.schemaRefRegistry;
278
+ pluginSchemas = resolveSpacePluginSchemas(context.space, config, context.configDir);
214
279
  }
215
280
  else if (!file) {
216
281
  console.error('Error: specify a file argument or use --space');
@@ -220,7 +285,49 @@ export function showSchema(file, options) {
220
285
  schemaPath = file;
221
286
  }
222
287
  else {
223
- schemaPath = join(bundledSchemasDir, file.endsWith('.json') ? file : `${file}.json`);
288
+ // Check discovered plugins
289
+ const discovered = await discoverPlugins();
290
+ pluginSchemas = discovered
291
+ .filter((p) => p.schemasDir)
292
+ .map((p) => ({ pluginName: shortenPluginName(p.name), schemasDir: p.schemasDir }));
293
+ let candidatePath;
294
+ if (file.includes('/')) {
295
+ const slashIdx = file.indexOf('/');
296
+ const prefix = file.slice(0, slashIdx);
297
+ const subPath = file.slice(slashIdx + 1);
298
+ const norm = normalizePluginName(prefix);
299
+ const short = shortenPluginName(prefix);
300
+ const pluginDir = resolvePluginSchemasDir(prefix, process.cwd());
301
+ if (pluginDir) {
302
+ candidatePath = resolve(pluginDir, subPath.endsWith('.json') ? subPath : `${subPath}.json`);
303
+ if (!pluginSchemas.some((ps) => ps.pluginName === short)) {
304
+ pluginSchemas.push({ pluginName: short, schemasDir: pluginDir });
305
+ }
306
+ }
307
+ else {
308
+ const match = discovered.find((p) => (p.name === norm || p.name === short) && p.schemasDir);
309
+ if (match?.schemasDir) {
310
+ candidatePath = resolve(match.schemasDir, subPath.endsWith('.json') ? subPath : `${subPath}.json`);
311
+ }
312
+ }
313
+ }
314
+ else {
315
+ for (const p of discovered) {
316
+ if (p.schemasDir) {
317
+ const c = resolve(p.schemasDir, file.endsWith('.json') ? file : `${file}.json`);
318
+ if (existsSync(c)) {
319
+ candidatePath = c;
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ if (candidatePath && existsSync(candidatePath)) {
326
+ schemaPath = candidatePath;
327
+ }
328
+ else {
329
+ schemaPath = join(bundledSchemasDir, file.endsWith('.json') ? file : `${file}.json`);
330
+ }
224
331
  }
225
332
  if (!existsSync(schemaPath)) {
226
333
  console.error(`Schema not found: ${schemaPath}`);
@@ -237,7 +344,11 @@ export function showSchema(file, options) {
237
344
  process.stdout.write(content);
238
345
  return;
239
346
  }
240
- const { schema, schemaRefRegistry } = loadSchema(schemaPath);
347
+ if (!schema || !schemaRefRegistry) {
348
+ const loaded = loadSchema(schemaPath, pluginSchemas);
349
+ schema = loaded.schema;
350
+ schemaRefRegistry = loaded.schemaRefRegistry;
351
+ }
241
352
  // Handle --mermaid-erd: generate ERD and exit
242
353
  if (options.mermaidErd) {
243
354
  const entityInfo = extractEntityInfo(schema, schemaRefRegistry);
@@ -265,5 +376,5 @@ export function showSchema(file, options) {
265
376
  console.log(` ${ref}`);
266
377
  }
267
378
  }
268
- showRegistry(schemaPath, schemaRefRegistry);
379
+ showRegistry(schemaPath, schemaRefRegistry, pluginSchemas);
269
380
  }
@@ -1,4 +1,5 @@
1
1
  import type { SpaceContext } from '../types';
2
2
  export declare function show(context: SpaceContext, options?: {
3
3
  filter?: string;
4
+ hierarchy?: string;
4
5
  }): Promise<void>;
@@ -1,6 +1,12 @@
1
1
  import { executeRender } from '../render/render';
2
2
  export async function show(context, options) {
3
- const result = await executeRender('markdown.bullets', context, { filter: options?.filter });
3
+ const result = await executeRender('markdown.bullets', context, {
4
+ filter: options?.filter,
5
+ hierarchy: options?.hierarchy,
6
+ });
7
+ if (typeof result !== 'string') {
8
+ throw new Error('Expected string output from markdown.bullets renderer');
9
+ }
4
10
  process.stdout.write(result);
5
11
  if (!result.endsWith('\n'))
6
12
  process.stdout.write('\n');
@@ -1 +1,3 @@
1
- export declare function listSpaces(): void;
1
+ export declare function listSpaces(options?: {
2
+ json?: boolean;
3
+ }): void;
@@ -1,13 +1,22 @@
1
1
  import { basename, resolve } from 'node:path';
2
- import { configPath, loadConfig } from '../config';
2
+ import { configPath, getConfigSourceFiles, loadConfig } from '../config';
3
3
  function renderConfigValue(v) {
4
4
  if (typeof v === 'object' && v !== null)
5
5
  return JSON.stringify(v);
6
6
  return String(v);
7
7
  }
8
- export function listSpaces() {
8
+ export function listSpaces(options = {}) {
9
9
  const path = resolve(configPath());
10
10
  const config = loadConfig();
11
+ if (options.json) {
12
+ const spaces = config.spaces.map((space) => ({
13
+ name: space.name,
14
+ path: space.path,
15
+ schema: space.schema ?? config.schema,
16
+ }));
17
+ console.log(JSON.stringify({ configFiles: [...getConfigSourceFiles()], spaces }));
18
+ return;
19
+ }
11
20
  console.log(`Config: ${path}\n`);
12
21
  for (const space of config.spaces) {
13
22
  console.log(`${space.name}`);
@@ -6,4 +6,7 @@ export { validateFile } from '../validate';
6
6
  */
7
7
  export declare function validateFileCommand(filePath: string, options?: {
8
8
  json?: boolean;
9
+ includeGated?: boolean;
10
+ semantic?: boolean | string;
11
+ ruleFilter?: string[];
9
12
  }): Promise<number>;
@@ -1,5 +1,8 @@
1
1
  import { loadConfig } from '../config';
2
- import { validateFile } from '../validate';
2
+ import { judgeForRun } from '../judge/factory';
3
+ import { parseRuleFilter } from '../schema/rule-filter';
4
+ import { createSpaceContext } from '../space-context';
5
+ import { fileNotInSpace, resolveFileSpace, validateResolvedFile } from '../validate';
3
6
  export { validateFile } from '../validate';
4
7
  /**
5
8
  * CLI command: validate a file and print results to stdout.
@@ -8,7 +11,19 @@ export { validateFile } from '../validate';
8
11
  */
9
12
  export async function validateFileCommand(filePath, options = {}) {
10
13
  const config = loadConfig();
11
- const result = await validateFile(filePath, config);
14
+ const resolved = resolveFileSpace(filePath, config);
15
+ let result;
16
+ if (resolved) {
17
+ const judge = judgeForRun(config, resolved.space, options.semantic);
18
+ result = await validateResolvedFile(filePath, resolved.label, createSpaceContext(resolved.space.name, config), {
19
+ includeGated: options.includeGated,
20
+ judge,
21
+ ruleFilter: options.ruleFilter && parseRuleFilter(options.ruleFilter),
22
+ });
23
+ }
24
+ else {
25
+ result = fileNotInSpace(filePath);
26
+ }
12
27
  if (!result.inSpace) {
13
28
  if (options.json) {
14
29
  console.log(JSON.stringify(result, null, 2));
@@ -34,8 +49,8 @@ function printHumanReadable(result) {
34
49
  }
35
50
  if (result.errorCount > 0) {
36
51
  console.log(`\n${red}✗${reset} ${result.label} (space: ${result.space}) — ${result.errorCount} error(s)\n`);
37
- for (const { kind, message } of Object.values(result.errors)) {
38
- console.log(` [${kind}] ${message}`);
52
+ for (const issue of Object.values(result.errors)) {
53
+ console.log(` [${issueLabel(issue)}] ${issue.message}`);
39
54
  }
40
55
  }
41
56
  else {
@@ -43,9 +58,12 @@ function printHumanReadable(result) {
43
58
  }
44
59
  if (result.warningCount > 0) {
45
60
  console.log(`\n ${yellow}${result.warningCount} warning(s):${reset}`);
46
- for (const { kind, message } of Object.values(result.warnings)) {
47
- console.log(` ${yellow}[${kind}]${reset} ${message}`);
61
+ for (const issue of Object.values(result.warnings)) {
62
+ console.log(` ${yellow}[${issueLabel(issue)}]${reset} ${issue.message}`);
48
63
  }
49
64
  }
50
65
  console.log('');
51
66
  }
67
+ function issueLabel({ kind, severity }) {
68
+ return severity ? `${kind}:${severity}` : kind;
69
+ }
@@ -1,5 +1,9 @@
1
1
  import type { SpaceContext } from '../types';
2
2
  export declare function validate(context: SpaceContext, options?: {
3
3
  json?: boolean;
4
+ depth?: number;
5
+ includeGated?: boolean;
6
+ semantic?: boolean | string;
7
+ ruleFilter?: string[];
4
8
  }): Promise<number>;
5
9
  export declare function watchValidate(context: SpaceContext): Promise<never>;