structured-context 0.13.0 → 0.14.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 +2 -2
- package/dist/api.d.ts +17 -17
- package/dist/api.js +12 -12
- package/dist/commands/dump.js +1 -1
- package/dist/commands/validate.js +8 -8
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -4
- package/dist/filter/augment-nodes.d.ts +5 -4
- package/dist/filter/augment-nodes.js +20 -19
- package/dist/filter/expand-include.d.ts +2 -2
- package/dist/filter/expand-include.js +15 -17
- package/dist/filter/filter-nodes.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -17
- package/dist/judge/context.d.ts +1 -1
- package/dist/judge/context.js +12 -12
- package/dist/judge/factory.d.ts +1 -1
- package/dist/judge/factory.js +1 -1
- package/dist/judge/index.d.ts +1 -1
- package/dist/judge/judge-node.d.ts +4 -4
- package/dist/judge/judge-node.js +7 -7
- package/dist/judge/prompt.d.ts +1 -1
- package/dist/judge/prompt.js +3 -3
- package/dist/judge/provider/fake.d.ts +1 -1
- package/dist/judge/provider/fake.js +3 -6
- package/dist/judge/provider/jev.js +1 -1
- package/dist/judge/provider/openai-decisions.js +1 -1
- package/dist/judge/trim.d.ts +1 -1
- package/dist/judge/types.d.ts +4 -1
- package/dist/judge/verdicts.d.ts +1 -1
- package/dist/load-space-graph.d.ts +3 -3
- package/dist/load-space-graph.js +4 -4
- package/dist/plugins/loader.js +1 -1
- package/dist/plugins/markdown/index.d.ts +6 -0
- package/dist/plugins/markdown/index.js +1 -0
- package/dist/plugins/markdown/parse-embedded.d.ts +7 -2
- package/dist/plugins/markdown/parse-embedded.js +23 -21
- package/dist/plugins/markdown/read-space.js +9 -8
- package/dist/plugins/markdown/render-bullets.js +3 -3
- package/dist/plugins/markdown/render-directory.js +1 -1
- package/dist/plugins/markdown/util.d.ts +12 -1
- package/dist/plugins/markdown/util.js +21 -0
- package/dist/plugins/mermaid/render-mermaid.js +5 -6
- package/dist/plugins/miro/cache.js +1 -1
- package/dist/plugins/miro/sync.js +3 -2
- package/dist/read/resolve-graph-edges.js +12 -18
- package/dist/render/render.d.ts +1 -1
- package/dist/render/render.js +1 -1
- package/dist/schema/evaluate-rule.d.ts +4 -4
- package/dist/schema/evaluate-rule.js +14 -10
- package/dist/schema/gating.d.ts +10 -10
- package/dist/schema/gating.js +45 -44
- package/dist/schema/validate-graph.js +8 -11
- package/dist/schema/validate-rules.d.ts +4 -4
- package/dist/schema/validate-rules.js +15 -17
- package/dist/schemas/_ost_strict.json +1 -1
- package/dist/space-context.d.ts +2 -2
- package/dist/space-context.js +3 -3
- package/dist/space-graph.d.ts +9 -9
- package/dist/space-graph.js +16 -16
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/dist/types.d.ts +18 -14
- package/dist/validate.d.ts +6 -6
- package/dist/validate.js +26 -27
- package/docs/concepts.md +25 -7
- package/docs/config.md +8 -0
- package/docs/rules.md +17 -13
- package/package.json +5 -5
- package/schemas/_ost_strict.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Content and structure
|
|
|
27
27
|
Valid 16
|
|
28
28
|
Schema validation errors 0
|
|
29
29
|
Broken links 1
|
|
30
|
-
Duplicate
|
|
30
|
+
Duplicate ids 0
|
|
31
31
|
Rule violations (error) 0
|
|
32
32
|
Rule violations (warning) 1
|
|
33
33
|
Rule violations (info) 1
|
|
@@ -138,7 +138,7 @@ Validates markdown files against the JSON schema:
|
|
|
138
138
|
- Extracts YAML frontmatter from each `.md` file
|
|
139
139
|
- Skips files without frontmatter or without a `type` field
|
|
140
140
|
- Reports validation results with counts and per-file errors
|
|
141
|
-
- Runs executable rules (`$metadata.rules`) with **cascade gating**: a schema, hierarchy
|
|
141
|
+
- Runs executable rules (`$metadata.rules`) with **cascade gating**: a schema, hierarchy or gating rule violation on a node suppresses rule evaluation for that node's hierarchy subtree. Gated rules are reported in a Coverage section so you know what wasn't evaluated and why. See [docs/rules.md](docs/rules.md#cascade-gating) for details.
|
|
142
142
|
- `--depth <n>` limits evaluation to hierarchy nodes within `n` breadth-first levels from the top — useful for staged agentic fix loops (fix the top level, then request the next).
|
|
143
143
|
- `--include-gated` evaluates rules even on gated nodes (still reported as gated) — for surfacing cheap bottom-up checks on request.
|
|
144
144
|
- `--semantic` also evaluates the `semantic` part of rules with the configured model provider (Jev, Anthropic or OpenAI-compatible); `--semantic <provider>` uses that configured model provider instead; `--rule-filter` narrows which rules run. See [docs/semantic-rules.md](docs/semantic-rules.md) (`sctx docs semantic-rules`).
|
package/dist/api.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Import from this module to use structured-context as a library:
|
|
5
5
|
*
|
|
6
|
-
* import { loadConfig, loadSpaceGraph, validateSpace, validateFile } from '
|
|
7
|
-
* import type { StructuredContextPlugin, PluginContext, ParseResult } from '
|
|
6
|
+
* import { loadConfig, loadSpaceGraph, validateSpace, validateFile } from 'structured-context/api';
|
|
7
|
+
* import type { StructuredContextPlugin, PluginContext, ParseResult } from 'structured-context/api';
|
|
8
8
|
*
|
|
9
9
|
* Common patterns:
|
|
10
10
|
*
|
|
@@ -18,18 +18,18 @@
|
|
|
18
18
|
* const validation = await validateSpace(ctx, { readResult });
|
|
19
19
|
*/
|
|
20
20
|
export type { AnySchemaObject, SchemaObject, ValidateFunction } from 'ajv';
|
|
21
|
-
export type { Config, SpaceConfig } from './config
|
|
22
|
-
export { loadConfig, resolveSchema, resolveSchemaPath, setConfigPath } from './config
|
|
23
|
-
export { PLUGIN_PREFIX } from './constants
|
|
24
|
-
export { assembleSpaceGraph, loadSpaceGraph } from './load-space-graph
|
|
25
|
-
export type { ParseHook, ParseResult, PluginContext, RenderFileOutput, RenderFormat, RenderHook, RenderMultiFileOutput, RenderOptions, RenderResult, StructuredContextPlugin, TemplateSyncHook, TemplateSyncOptions, } from './plugins/util
|
|
26
|
-
export { isRenderMultiFileOutput, resolvePluginDir, resolvePluginSchemasDir, resolveSpacePluginSchemas, } from './plugins/util
|
|
27
|
-
export { readSpace } from './read/read-space
|
|
28
|
-
export { buildTargetIndex, formatWikilink, type ParsedWikilink, parseWikilink, wikilinkToTarget, } from './read/wikilink-utils
|
|
29
|
-
export type { SharedEmbeddingFields } from './schema/metadata-contract
|
|
30
|
-
export { bundledSchemasDir, loadSchema, type PluginSchemaSource, setBundledSchemasDir } from './schema/schema
|
|
31
|
-
export { createSpaceContext, SpaceNotFoundError } from './space-context
|
|
32
|
-
export { buildSpaceGraph, type SpaceGraph } from './space-graph
|
|
33
|
-
export type { BaseNode, ContentLink, EdgeDefinition, FileNotInSpaceResult, FileValidationIssue, FileValidationResult, Hierarchy, HierarchyLevel, ParseIssue, ReadSpaceResult, Relationship, ResolvedContentLink, RuleCategory, RuleSeverity, RuleViolation, SchemaMetadata, SchemaWithMetadata, SourceLocation, SpaceContext, SpaceNode, UnresolvedRef, ValidateFileOutput, } from './types
|
|
34
|
-
export type { ValidationResult } from './validate
|
|
35
|
-
export { validateFile, validateSpace } from './validate
|
|
21
|
+
export type { Config, SpaceConfig } from './config';
|
|
22
|
+
export { loadConfig, resolveSchema, resolveSchemaPath, setConfigPath } from './config';
|
|
23
|
+
export { PLUGIN_PREFIX } from './constants';
|
|
24
|
+
export { assembleSpaceGraph, loadSpaceGraph } from './load-space-graph';
|
|
25
|
+
export type { ParseHook, ParseResult, PluginContext, RenderFileOutput, RenderFormat, RenderHook, RenderMultiFileOutput, RenderOptions, RenderResult, StructuredContextPlugin, TemplateSyncHook, TemplateSyncOptions, } from './plugins/util';
|
|
26
|
+
export { isRenderMultiFileOutput, resolvePluginDir, resolvePluginSchemasDir, resolveSpacePluginSchemas, } from './plugins/util';
|
|
27
|
+
export { readSpace } from './read/read-space';
|
|
28
|
+
export { buildTargetIndex, formatWikilink, type ParsedWikilink, parseWikilink, wikilinkToTarget, } from './read/wikilink-utils';
|
|
29
|
+
export type { SharedEmbeddingFields } from './schema/metadata-contract';
|
|
30
|
+
export { bundledSchemasDir, loadSchema, type PluginSchemaSource, setBundledSchemasDir } from './schema/schema';
|
|
31
|
+
export { createSpaceContext, SpaceNotFoundError } from './space-context';
|
|
32
|
+
export { buildSpaceGraph, type SpaceGraph } from './space-graph';
|
|
33
|
+
export type { BaseNode, ContentLink, EdgeDefinition, FileNotInSpaceResult, FileValidationIssue, FileValidationResult, Hierarchy, HierarchyLevel, ParseIssue, ReadSpaceResult, Relationship, ResolvedContentLink, RuleCategory, RuleSeverity, RuleViolation, SchemaMetadata, SchemaWithMetadata, SourceLocation, SpaceContext, SpaceNode, UnresolvedRef, ValidateFileOutput, } from './types';
|
|
34
|
+
export type { ValidationResult } from './validate';
|
|
35
|
+
export { validateFile, validateSpace } from './validate';
|
package/dist/api.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Import from this module to use structured-context as a library:
|
|
5
5
|
*
|
|
6
|
-
* import { loadConfig, loadSpaceGraph, validateSpace, validateFile } from '
|
|
7
|
-
* import type { StructuredContextPlugin, PluginContext, ParseResult } from '
|
|
6
|
+
* import { loadConfig, loadSpaceGraph, validateSpace, validateFile } from 'structured-context/api';
|
|
7
|
+
* import type { StructuredContextPlugin, PluginContext, ParseResult } from 'structured-context/api';
|
|
8
8
|
*
|
|
9
9
|
* Common patterns:
|
|
10
10
|
*
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
* const graph = await assembleSpaceGraph(ctx, { readResult });
|
|
18
18
|
* const validation = await validateSpace(ctx, { readResult });
|
|
19
19
|
*/
|
|
20
|
-
export { loadConfig, resolveSchema, resolveSchemaPath, setConfigPath } from './config
|
|
21
|
-
export { PLUGIN_PREFIX } from './constants
|
|
22
|
-
export { assembleSpaceGraph, loadSpaceGraph } from './load-space-graph
|
|
23
|
-
export { isRenderMultiFileOutput, resolvePluginDir, resolvePluginSchemasDir, resolveSpacePluginSchemas, } from './plugins/util
|
|
24
|
-
export { readSpace } from './read/read-space
|
|
25
|
-
export { buildTargetIndex, formatWikilink, parseWikilink, wikilinkToTarget, } from './read/wikilink-utils
|
|
26
|
-
export { bundledSchemasDir, loadSchema, setBundledSchemasDir } from './schema/schema
|
|
27
|
-
export { createSpaceContext, SpaceNotFoundError } from './space-context
|
|
28
|
-
export { buildSpaceGraph } from './space-graph
|
|
29
|
-
export { validateFile, validateSpace } from './validate
|
|
20
|
+
export { loadConfig, resolveSchema, resolveSchemaPath, setConfigPath } from './config';
|
|
21
|
+
export { PLUGIN_PREFIX } from './constants';
|
|
22
|
+
export { assembleSpaceGraph, loadSpaceGraph } from './load-space-graph';
|
|
23
|
+
export { isRenderMultiFileOutput, resolvePluginDir, resolvePluginSchemasDir, resolveSpacePluginSchemas, } from './plugins/util';
|
|
24
|
+
export { readSpace } from './read/read-space';
|
|
25
|
+
export { buildTargetIndex, formatWikilink, parseWikilink, wikilinkToTarget, } from './read/wikilink-utils';
|
|
26
|
+
export { bundledSchemasDir, loadSchema, setBundledSchemasDir } from './schema/schema';
|
|
27
|
+
export { createSpaceContext, SpaceNotFoundError } from './space-context';
|
|
28
|
+
export { buildSpaceGraph } from './space-graph';
|
|
29
|
+
export { validateFile, validateSpace } from './validate';
|
package/dist/commands/dump.js
CHANGED
|
@@ -11,7 +11,7 @@ export async function buildDump(context, options = {}) {
|
|
|
11
11
|
if (!options.filter)
|
|
12
12
|
return readResult;
|
|
13
13
|
const graph = await assembleSpaceGraph(context, { ...options, readResult });
|
|
14
|
-
const nodes = readResult.nodes.filter((node) => graph.nodes.get(node.
|
|
14
|
+
const nodes = readResult.nodes.filter((node) => graph.nodes.get(node.id) === node);
|
|
15
15
|
return { ...readResult, nodes };
|
|
16
16
|
}
|
|
17
17
|
export async function dump(context, options = {}) {
|
|
@@ -34,10 +34,10 @@ export async function validate(context, options = {}) {
|
|
|
34
34
|
for (const { file, parent, error } of result.refErrors) {
|
|
35
35
|
addError(file, `broken-link:${parent}`, 'broken-link', `${parent} → ${error}`);
|
|
36
36
|
}
|
|
37
|
-
for (const {
|
|
37
|
+
for (const { id, files } of result.duplicateErrors) {
|
|
38
38
|
for (const file of files) {
|
|
39
39
|
const others = files.filter((f) => f !== file);
|
|
40
|
-
addError(file, `duplicate:${
|
|
40
|
+
addError(file, `duplicate:${id}`, 'duplicate', `Duplicate id "${id}" also exists in: ${others.join(', ')}`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
for (const v of result.ruleViolations) {
|
|
@@ -97,7 +97,7 @@ export async function validate(context, options = {}) {
|
|
|
97
97
|
console.log(fmt(' Valid', result.validCount));
|
|
98
98
|
console.log(fmt(' Schema validation errors', result.nodeErrorCount, true));
|
|
99
99
|
console.log(fmt(' Broken links', result.refErrors.length, true));
|
|
100
|
-
console.log(fmt(' Duplicate
|
|
100
|
+
console.log(fmt(' Duplicate ids', result.duplicateErrors.length, true));
|
|
101
101
|
console.log(fmt(' Rule violations (error)', ruleCounts.error, true));
|
|
102
102
|
console.log(fmt(' Rule violations (warning)', ruleCounts.warning, true, true));
|
|
103
103
|
console.log(fmt(' Rule violations (info)', ruleCounts.info, true, true));
|
|
@@ -114,7 +114,7 @@ export async function validate(context, options = {}) {
|
|
|
114
114
|
if (result.orphans.length > 0) {
|
|
115
115
|
console.log(`\nOrphans (hierarchy nodes - no parent):`);
|
|
116
116
|
for (const node of result.orphans)
|
|
117
|
-
console.log(` ${node.
|
|
117
|
+
console.log(` ${node.id}`);
|
|
118
118
|
}
|
|
119
119
|
if (result.unresolvedContentLinks.length > 0) {
|
|
120
120
|
console.log(`\nUnresolved content links (may be outside-space or broken):`);
|
|
@@ -157,9 +157,9 @@ export async function validate(context, options = {}) {
|
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
159
|
if (result.duplicateErrors.length > 0) {
|
|
160
|
-
console.log(`\nDuplicate
|
|
161
|
-
result.duplicateErrors.forEach(({
|
|
162
|
-
console.log(` "${
|
|
160
|
+
console.log(`\nDuplicate ids (same id on multiple nodes):`);
|
|
161
|
+
result.duplicateErrors.forEach(({ id, files }) => {
|
|
162
|
+
console.log(` "${id}":`);
|
|
163
163
|
for (const f of files) {
|
|
164
164
|
console.log(` ${f}`);
|
|
165
165
|
}
|
|
@@ -194,7 +194,7 @@ export async function validate(context, options = {}) {
|
|
|
194
194
|
console.log(`\nCoverage — rule checks gated by cascade (use --include-gated to evaluate anyway):`);
|
|
195
195
|
for (const gate of result.coverageGates) {
|
|
196
196
|
const cause = gate.reason === 'rule' ? `blocking rule "${gate.ruleId}"` : `${gate.reason} failure`;
|
|
197
|
-
console.log(` ${gate.file} (${gate.
|
|
197
|
+
console.log(` ${gate.file} (${gate.nodeId}) — ${cause} gated ${gate.gatedFiles.length} node(s):`);
|
|
198
198
|
for (const file of gate.gatedFiles)
|
|
199
199
|
console.log(` ${file}`);
|
|
200
200
|
}
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -3,10 +3,10 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import Ajv2020 from 'ajv/dist/2020';
|
|
5
5
|
import JSON5 from 'json5';
|
|
6
|
-
import { ENV_CONFIG_VAR, XDG_CONFIG_DIR } from './constants
|
|
7
|
-
import { MODEL_PROVIDER_APIS } from './judge/types
|
|
8
|
-
import { normalizePluginName, resolvePluginSchemasDir, resolveSpacePluginSchemas, shortenPluginName, } from './plugins/util
|
|
9
|
-
import { bundledSchemasDir } from './schema/schema
|
|
6
|
+
import { ENV_CONFIG_VAR, XDG_CONFIG_DIR } from './constants';
|
|
7
|
+
import { MODEL_PROVIDER_APIS } from './judge/types';
|
|
8
|
+
import { normalizePluginName, resolvePluginSchemasDir, resolveSpacePluginSchemas, shortenPluginName, } from './plugins/util';
|
|
9
|
+
import { bundledSchemasDir } from './schema/schema';
|
|
10
10
|
const MODEL_PROVIDER_SCHEMA = {
|
|
11
11
|
type: 'object',
|
|
12
12
|
properties: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SpaceNode } from '../types';
|
|
1
|
+
import type { NodeId, SpaceNode } from '../types';
|
|
2
2
|
/** Edge metadata merged into each ancestor/descendant entry. */
|
|
3
3
|
export type EdgeMetadata = {
|
|
4
4
|
_field: string;
|
|
@@ -11,6 +11,7 @@ export type EdgeMetadata = {
|
|
|
11
11
|
export type AugmentedFlatNode = Record<string, unknown> & {
|
|
12
12
|
resolvedType: string;
|
|
13
13
|
resolvedParentTitles: string[];
|
|
14
|
+
resolvedParentIds: NodeId[];
|
|
14
15
|
ancestors: Array<Record<string, unknown> & EdgeMetadata>;
|
|
15
16
|
descendants: Array<Record<string, unknown> & EdgeMetadata>;
|
|
16
17
|
};
|
|
@@ -18,8 +19,8 @@ export type AugmentedFlatNode = Record<string, unknown> & {
|
|
|
18
19
|
* Build the augmented flat representation of a node, including pre-computed
|
|
19
20
|
* ancestors[] and descendants[] arrays with edge metadata.
|
|
20
21
|
*
|
|
21
|
-
* - ancestors: BFS from node via resolvedParents, nearest first, deduplicated by
|
|
22
|
-
* - descendants: BFS via childrenIndex, nearest first, deduplicated by
|
|
22
|
+
* - ancestors: BFS from node via resolvedParents, nearest first, deduplicated by id.
|
|
23
|
+
* - descendants: BFS via childrenIndex, nearest first, deduplicated by id.
|
|
23
24
|
* - Each entry merges the parent/child node's fields with edge metadata (_field, _source, _hierarchy, _selfRef).
|
|
24
25
|
*/
|
|
25
|
-
export declare function augmentNode(node: SpaceNode, nodeIndex: ReadonlyMap<
|
|
26
|
+
export declare function augmentNode(node: SpaceNode, nodeIndex: ReadonlyMap<NodeId, SpaceNode>, childrenIndex: ReadonlyMap<NodeId, readonly SpaceNode[]>): AugmentedFlatNode;
|
|
@@ -11,7 +11,7 @@ function edgeMetadata(ref) {
|
|
|
11
11
|
function flattenData(node) {
|
|
12
12
|
return {
|
|
13
13
|
...node.schemaData,
|
|
14
|
-
|
|
14
|
+
id: node.id,
|
|
15
15
|
title: node.title,
|
|
16
16
|
resolvedType: node.resolvedType,
|
|
17
17
|
};
|
|
@@ -20,8 +20,8 @@ function flattenData(node) {
|
|
|
20
20
|
* Build the augmented flat representation of a node, including pre-computed
|
|
21
21
|
* ancestors[] and descendants[] arrays with edge metadata.
|
|
22
22
|
*
|
|
23
|
-
* - ancestors: BFS from node via resolvedParents, nearest first, deduplicated by
|
|
24
|
-
* - descendants: BFS via childrenIndex, nearest first, deduplicated by
|
|
23
|
+
* - ancestors: BFS from node via resolvedParents, nearest first, deduplicated by id.
|
|
24
|
+
* - descendants: BFS via childrenIndex, nearest first, deduplicated by id.
|
|
25
25
|
* - Each entry merges the parent/child node's fields with edge metadata (_field, _source, _hierarchy, _selfRef).
|
|
26
26
|
*/
|
|
27
27
|
export function augmentNode(node, nodeIndex, childrenIndex) {
|
|
@@ -30,7 +30,8 @@ export function augmentNode(node, nodeIndex, childrenIndex) {
|
|
|
30
30
|
return {
|
|
31
31
|
...flattenData(node),
|
|
32
32
|
resolvedType: node.resolvedType,
|
|
33
|
-
resolvedParentTitles: node.resolvedParents.map((r) => r.title),
|
|
33
|
+
resolvedParentTitles: node.resolvedParents.map((r) => nodeIndex.get(r.id)?.title ?? r.id),
|
|
34
|
+
resolvedParentIds: node.resolvedParents.map((r) => r.id),
|
|
34
35
|
ancestors,
|
|
35
36
|
descendants,
|
|
36
37
|
};
|
|
@@ -41,24 +42,24 @@ function buildAncestors(node, nodeIndex) {
|
|
|
41
42
|
// BFS queue holds: { parentRef that led to this node, the node itself }
|
|
42
43
|
const queue = [];
|
|
43
44
|
for (const ref of node.resolvedParents) {
|
|
44
|
-
const parentNode = nodeIndex.get(ref.
|
|
45
|
+
const parentNode = nodeIndex.get(ref.id);
|
|
45
46
|
if (parentNode)
|
|
46
47
|
queue.push({ ref, node: parentNode });
|
|
47
48
|
}
|
|
48
49
|
while (queue.length > 0) {
|
|
49
50
|
const item = queue.shift();
|
|
50
|
-
const
|
|
51
|
-
if (visited.has(
|
|
51
|
+
const id = item.node.id;
|
|
52
|
+
if (visited.has(id))
|
|
52
53
|
continue;
|
|
53
|
-
visited.add(
|
|
54
|
+
visited.add(id);
|
|
54
55
|
result.push({
|
|
55
56
|
...flattenData(item.node),
|
|
56
57
|
...edgeMetadata(item.ref),
|
|
57
58
|
});
|
|
58
59
|
// Continue BFS upward
|
|
59
60
|
for (const ref of item.node.resolvedParents) {
|
|
60
|
-
const parentNode = nodeIndex.get(ref.
|
|
61
|
-
if (parentNode && !visited.has(ref.
|
|
61
|
+
const parentNode = nodeIndex.get(ref.id);
|
|
62
|
+
if (parentNode && !visited.has(ref.id)) {
|
|
62
63
|
queue.push({ ref, node: parentNode });
|
|
63
64
|
}
|
|
64
65
|
}
|
|
@@ -66,31 +67,31 @@ function buildAncestors(node, nodeIndex) {
|
|
|
66
67
|
return result;
|
|
67
68
|
}
|
|
68
69
|
function buildDescendants(node, childrenIndex) {
|
|
69
|
-
const
|
|
70
|
+
const nodeId = node.id;
|
|
70
71
|
const visited = new Set();
|
|
71
72
|
const result = [];
|
|
72
73
|
// BFS queue holds: child node + the resolvedParents entry on that child that points to its parent
|
|
73
74
|
const queue = [];
|
|
74
|
-
const directChildren = childrenIndex.get(
|
|
75
|
+
const directChildren = childrenIndex.get(nodeId) ?? [];
|
|
75
76
|
for (const child of directChildren) {
|
|
76
|
-
const ref = child.resolvedParents.find((r) => r.
|
|
77
|
+
const ref = child.resolvedParents.find((r) => r.id === nodeId);
|
|
77
78
|
if (ref)
|
|
78
79
|
queue.push({ childNode: child, ref });
|
|
79
80
|
}
|
|
80
81
|
while (queue.length > 0) {
|
|
81
82
|
const item = queue.shift();
|
|
82
|
-
const
|
|
83
|
-
if (visited.has(
|
|
83
|
+
const id = item.childNode.id;
|
|
84
|
+
if (visited.has(id))
|
|
84
85
|
continue;
|
|
85
|
-
visited.add(
|
|
86
|
+
visited.add(id);
|
|
86
87
|
result.push({
|
|
87
88
|
...flattenData(item.childNode),
|
|
88
89
|
...edgeMetadata(item.ref),
|
|
89
90
|
});
|
|
90
|
-
const grandchildren = childrenIndex.get(
|
|
91
|
+
const grandchildren = childrenIndex.get(id) ?? [];
|
|
91
92
|
for (const grandchild of grandchildren) {
|
|
92
|
-
if (!visited.has(grandchild.
|
|
93
|
-
const ref = grandchild.resolvedParents.find((r) => r.
|
|
93
|
+
if (!visited.has(grandchild.id)) {
|
|
94
|
+
const ref = grandchild.resolvedParents.find((r) => r.id === id);
|
|
94
95
|
if (ref)
|
|
95
96
|
queue.push({ childNode: grandchild, ref });
|
|
96
97
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SpaceNode } from '../types';
|
|
1
|
+
import type { NodeId, SpaceNode } from '../types';
|
|
2
2
|
import type { AugmentedFlatNode } from './augment-nodes';
|
|
3
3
|
export type AncestorsDirective = {
|
|
4
4
|
kind: 'ancestors';
|
|
@@ -59,4 +59,4 @@ export declare function parseIncludeSpec(spec: string): IncludeDirective[];
|
|
|
59
59
|
* @param childrenIndex - Title → direct children (all edges)
|
|
60
60
|
* @param augmented - Title → AugmentedFlatNode with pre-computed ancestors/descendants
|
|
61
61
|
*/
|
|
62
|
-
export declare function expandInclude(matchedNodes: SpaceNode[], directives: IncludeDirective[], nodeIndex: ReadonlyMap<
|
|
62
|
+
export declare function expandInclude(matchedNodes: SpaceNode[], directives: IncludeDirective[], nodeIndex: ReadonlyMap<NodeId, SpaceNode>, childrenIndex: ReadonlyMap<NodeId, readonly SpaceNode[]>, augmented: Map<NodeId, AugmentedFlatNode>): SpaceNode[];
|
|
@@ -85,34 +85,33 @@ function parseDirective(item) {
|
|
|
85
85
|
export function expandInclude(matchedNodes, directives, nodeIndex, childrenIndex, augmented) {
|
|
86
86
|
if (directives.length === 0)
|
|
87
87
|
return matchedNodes;
|
|
88
|
-
const seen = new Set(matchedNodes.map((n) => n.
|
|
88
|
+
const seen = new Set(matchedNodes.map((n) => n.id));
|
|
89
89
|
const result = [...matchedNodes];
|
|
90
|
-
function
|
|
91
|
-
if (seen.has(
|
|
90
|
+
function addById(id) {
|
|
91
|
+
if (seen.has(id))
|
|
92
92
|
return;
|
|
93
|
-
const node = nodeIndex.get(
|
|
93
|
+
const node = nodeIndex.get(id);
|
|
94
94
|
if (node) {
|
|
95
|
-
seen.add(
|
|
95
|
+
seen.add(id);
|
|
96
96
|
result.push(node);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
for (const node of matchedNodes) {
|
|
100
|
-
const
|
|
101
|
-
const aug = augmented.get(title);
|
|
100
|
+
const aug = augmented.get(node.id);
|
|
102
101
|
if (!aug)
|
|
103
102
|
continue;
|
|
104
103
|
for (const directive of directives) {
|
|
105
|
-
applyDirective(node, aug, directive, childrenIndex,
|
|
104
|
+
applyDirective(node, aug, directive, childrenIndex, addById);
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
return result;
|
|
109
108
|
}
|
|
110
|
-
function applyDirective(node, aug, directive, childrenIndex,
|
|
109
|
+
function applyDirective(node, aug, directive, childrenIndex, addById) {
|
|
111
110
|
switch (directive.kind) {
|
|
112
111
|
case 'ancestors': {
|
|
113
112
|
for (const a of aug.ancestors) {
|
|
114
113
|
if (!directive.typeFilter || a.resolvedType === directive.typeFilter) {
|
|
115
|
-
|
|
114
|
+
addById(a.id);
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
break;
|
|
@@ -120,7 +119,7 @@ function applyDirective(node, aug, directive, childrenIndex, addByTitle) {
|
|
|
120
119
|
case 'descendants': {
|
|
121
120
|
for (const d of aug.descendants) {
|
|
122
121
|
if (!directive.typeFilter || d.resolvedType === directive.typeFilter) {
|
|
123
|
-
|
|
122
|
+
addById(d.id);
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
break;
|
|
@@ -128,11 +127,10 @@ function applyDirective(node, aug, directive, childrenIndex, addByTitle) {
|
|
|
128
127
|
case 'siblings': {
|
|
129
128
|
// Nodes that share at least one parent with the matched node (any edge type)
|
|
130
129
|
for (const parentRef of node.resolvedParents) {
|
|
131
|
-
const siblings = childrenIndex.get(parentRef.
|
|
130
|
+
const siblings = childrenIndex.get(parentRef.id) ?? [];
|
|
132
131
|
for (const sibling of siblings) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
addByTitle(siblingTitle);
|
|
132
|
+
if (sibling.id !== node.id) {
|
|
133
|
+
addById(sibling.id);
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
136
|
}
|
|
@@ -142,13 +140,13 @@ function applyDirective(node, aug, directive, childrenIndex, addByTitle) {
|
|
|
142
140
|
// Relationship-sourced ancestors (matched node is the child side)
|
|
143
141
|
for (const a of aug.ancestors) {
|
|
144
142
|
if (a._source === 'relationship' && matchesRelSpec(node, a, directive, true)) {
|
|
145
|
-
|
|
143
|
+
addById(a.id);
|
|
146
144
|
}
|
|
147
145
|
}
|
|
148
146
|
// Relationship-sourced descendants (matched node is the parent side)
|
|
149
147
|
for (const d of aug.descendants) {
|
|
150
148
|
if (d._source === 'relationship' && matchesRelSpec(node, d, directive, false)) {
|
|
151
|
-
|
|
149
|
+
addById(d.id);
|
|
152
150
|
}
|
|
153
151
|
}
|
|
154
152
|
break;
|
|
@@ -30,7 +30,7 @@ export async function filterNodes(expression, graph) {
|
|
|
30
30
|
// Pre-augment all nodes once (ancestors/descendants needed for WHERE predicates and SELECT expansion)
|
|
31
31
|
const augmented = new Map();
|
|
32
32
|
for (const node of nodeIndex.values()) {
|
|
33
|
-
augmented.set(node.
|
|
33
|
+
augmented.set(node.id, augmentNode(node, nodeIndex, childrenIndex));
|
|
34
34
|
}
|
|
35
35
|
// Step 1: apply WHERE clause to get the matched set
|
|
36
36
|
let matched;
|
|
@@ -48,7 +48,7 @@ export async function filterNodes(expression, graph) {
|
|
|
48
48
|
}
|
|
49
49
|
matched = [];
|
|
50
50
|
for (const node of nodeIndex.values()) {
|
|
51
|
-
const current = augmented.get(node.
|
|
51
|
+
const current = augmented.get(node.id);
|
|
52
52
|
if (!current)
|
|
53
53
|
continue;
|
|
54
54
|
// Spread current node fields at root level so bare field names work in expressions
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { format } from 'node:util';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { diagram } from './commands/diagram
|
|
6
|
-
import { docs } from './commands/docs
|
|
7
|
-
import { dump } from './commands/dump
|
|
8
|
-
import { miroSyncCommand } from './commands/miro-sync
|
|
9
|
-
import { listPlugins } from './commands/plugins
|
|
10
|
-
import { render, renderList } from './commands/render
|
|
11
|
-
import { listSchemas, showSchema } from './commands/schemas
|
|
12
|
-
import { show } from './commands/show
|
|
13
|
-
import { listSpaces } from './commands/spaces
|
|
14
|
-
import { templateSync } from './commands/template-sync
|
|
15
|
-
import { validate, watchValidate } from './commands/validate
|
|
16
|
-
import { validateFileCommand } from './commands/validate-file
|
|
17
|
-
import { loadConfig, setConfigPath } from './config
|
|
18
|
-
import { CLI_NAME } from './constants
|
|
19
|
-
import { JudgeConfigError } from './judge/factory
|
|
20
|
-
import { createSpaceContext, SpaceNotFoundError } from './space-context
|
|
5
|
+
import { diagram } from './commands/diagram';
|
|
6
|
+
import { docs } from './commands/docs';
|
|
7
|
+
import { dump } from './commands/dump';
|
|
8
|
+
import { miroSyncCommand } from './commands/miro-sync';
|
|
9
|
+
import { listPlugins } from './commands/plugins';
|
|
10
|
+
import { render, renderList } from './commands/render';
|
|
11
|
+
import { listSchemas, showSchema } from './commands/schemas';
|
|
12
|
+
import { show } from './commands/show';
|
|
13
|
+
import { listSpaces } from './commands/spaces';
|
|
14
|
+
import { templateSync } from './commands/template-sync';
|
|
15
|
+
import { validate, watchValidate } from './commands/validate';
|
|
16
|
+
import { validateFileCommand } from './commands/validate-file';
|
|
17
|
+
import { loadConfig, setConfigPath } from './config';
|
|
18
|
+
import { CLI_NAME } from './constants';
|
|
19
|
+
import { JudgeConfigError } from './judge/factory';
|
|
20
|
+
import { createSpaceContext, SpaceNotFoundError } from './space-context';
|
|
21
21
|
// Once node:process is loaded (vfile, via unified, loads it), Bun's console.log drops piped output
|
|
22
22
|
// past 64KB when the reader is slow. process.stdout.write delivers all of it before exit.
|
|
23
23
|
console.log = (...args) => {
|
|
@@ -157,7 +157,7 @@ providersCmd
|
|
|
157
157
|
.description('List bundled and configured model providers; keys are never shown')
|
|
158
158
|
.option('--json', 'Output as JSON')
|
|
159
159
|
.action(async (options) => {
|
|
160
|
-
const { listProviders } = await import('./commands/providers
|
|
160
|
+
const { listProviders } = await import('./commands/providers');
|
|
161
161
|
listProviders(options);
|
|
162
162
|
});
|
|
163
163
|
program.addCommand(providersCmd);
|
package/dist/judge/context.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SEMANTIC_SCOPES } from '../schema/metadata-contract';
|
|
2
2
|
import type { SpaceGraph } from '../space-graph';
|
|
3
3
|
import type { Rule, SpaceNode } from '../types';
|
|
4
|
-
import type { RelatedNode, SemanticJudgeInput } from '
|
|
4
|
+
import type { RelatedNode, SemanticJudgeInput } from './types';
|
|
5
5
|
export type SemanticScope = Exclude<(typeof SEMANTIC_SCOPES)[number], 'branch'>;
|
|
6
6
|
export type ContentMode = 'none' | 'nodeOnly' | 'fullScope';
|
|
7
7
|
/** What a rule asks to send besides the evaluated node's data. */
|
package/dist/judge/context.js
CHANGED
|
@@ -25,14 +25,14 @@ function dataWithBody(node, withBody) {
|
|
|
25
25
|
return data;
|
|
26
26
|
}
|
|
27
27
|
/** The evaluated node as a judge sees it. */
|
|
28
|
-
function judgeNodeView(node, withBody) {
|
|
28
|
+
function judgeNodeView(node, withBody, graph) {
|
|
29
29
|
return {
|
|
30
|
-
|
|
30
|
+
id: node.id,
|
|
31
31
|
title: node.title,
|
|
32
32
|
type: node.type,
|
|
33
33
|
resolvedType: node.resolvedType,
|
|
34
34
|
schemaData: dataWithBody(node, withBody),
|
|
35
|
-
|
|
35
|
+
parentTitles: node.resolvedParents.map((p) => graph.nodes.get(p.id)?.title ?? p.id),
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
function relatedView(node, relation, withBody) {
|
|
@@ -50,28 +50,28 @@ export function relatedNodes(graph, node, context) {
|
|
|
50
50
|
? []
|
|
51
51
|
: n.resolvedParents
|
|
52
52
|
.filter((p) => p.source === 'hierarchy' && p.hierarchy === hierarchyName)
|
|
53
|
-
.map((p) => graph.nodes.get(p.
|
|
53
|
+
.map((p) => graph.nodes.get(p.id))
|
|
54
54
|
.filter((p) => p !== undefined);
|
|
55
|
-
const childrenOf = (n) => graph.hierarchyChildren.get(n.
|
|
55
|
+
const childrenOf = (n) => graph.hierarchyChildren.get(n.id) ?? [];
|
|
56
56
|
const found = new Map();
|
|
57
57
|
const add = (other, relation) => {
|
|
58
|
-
if (other.
|
|
58
|
+
if (other.id === node.id)
|
|
59
59
|
return;
|
|
60
|
-
const existing = found.get(other.
|
|
60
|
+
const existing = found.get(other.id);
|
|
61
61
|
if (!existing)
|
|
62
|
-
found.set(other.
|
|
62
|
+
found.set(other.id, relatedView(other, relation, withBody));
|
|
63
63
|
else if (!existing.relations.includes(relation))
|
|
64
64
|
existing.relations.push(relation);
|
|
65
65
|
};
|
|
66
66
|
const reach = (next, relation) => {
|
|
67
|
-
const seen = new Set([node.
|
|
67
|
+
const seen = new Set([node.id]);
|
|
68
68
|
let level = next(node);
|
|
69
69
|
while (level.length > 0) {
|
|
70
70
|
const following = [];
|
|
71
71
|
for (const n of level) {
|
|
72
|
-
if (seen.has(n.
|
|
72
|
+
if (seen.has(n.id))
|
|
73
73
|
continue;
|
|
74
|
-
seen.add(n.
|
|
74
|
+
seen.add(n.id);
|
|
75
75
|
add(n, relation);
|
|
76
76
|
following.push(...next(n));
|
|
77
77
|
}
|
|
@@ -99,7 +99,7 @@ export function relatedNodes(graph, node, context) {
|
|
|
99
99
|
export function judgeContext(graph, node, context) {
|
|
100
100
|
const related = relatedNodes(graph, node, context);
|
|
101
101
|
return {
|
|
102
|
-
node: judgeNodeView(node, context.content !== 'none'),
|
|
102
|
+
node: judgeNodeView(node, context.content !== 'none', graph),
|
|
103
103
|
...(related.length > 0 && { related }),
|
|
104
104
|
};
|
|
105
105
|
}
|
package/dist/judge/factory.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HydratedProvider, type ModelProviderConfig, type SemanticJudgeProvider } from '
|
|
1
|
+
import { type HydratedProvider, type ModelProviderConfig, type SemanticJudgeProvider } from './types';
|
|
2
2
|
type Env = Record<string, string | undefined>;
|
|
3
3
|
/**
|
|
4
4
|
* Bundled providers: usable as a judge by name, without a `modelProviders` entry. A user entry of the
|
package/dist/judge/factory.js
CHANGED
|
@@ -3,7 +3,7 @@ import { FakeJudgeProvider } from './provider/fake';
|
|
|
3
3
|
import { JEV_LATEST, JevJudgeProvider } from './provider/jev';
|
|
4
4
|
import { OpenAIJudgeProvider, RECOMMENDED_OPENAI_MODEL } from './provider/openai';
|
|
5
5
|
import { OpenAIDecisionsJudgeProvider, RECOMMENDED_OPENAI_DECISIONS_MODEL } from './provider/openai-decisions';
|
|
6
|
-
import { MODEL_PROVIDER_APIS, } from '
|
|
6
|
+
import { MODEL_PROVIDER_APIS, } from './types';
|
|
7
7
|
const OPENROUTER_COMPLETIONS_ENDPOINT = 'https://openrouter.ai/api/v1';
|
|
8
8
|
const RECOMMENDED_OPENROUTER_OPENAI_DECISIONS_MODEL = `openai/${RECOMMENDED_OPENAI_DECISIONS_MODEL}-decisions`;
|
|
9
9
|
const RECOMMENDED_OPENROUTER_OPENAI_MODEL = `openai/${RECOMMENDED_OPENAI_MODEL}`;
|
package/dist/judge/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export { JevJudgeProvider } from './provider/jev';
|
|
|
9
9
|
export type { OpenAIJudgeProviderOptions } from './provider/openai';
|
|
10
10
|
export { OpenAIJudgeProvider } from './provider/openai';
|
|
11
11
|
export { PerRuleJudgeProvider } from './provider/per-rule';
|
|
12
|
-
export type { JudgeConfig, ModelProviderConfig, SemanticJudgeInput, SemanticJudgeProvider, SemanticJudgeResult, SemanticJudgeVerdict, SemanticRuleSpec, } from '
|
|
12
|
+
export type { JudgeConfig, ModelProviderConfig, SemanticJudgeInput, SemanticJudgeProvider, SemanticJudgeResult, SemanticJudgeVerdict, SemanticRuleSpec, } from './types';
|
|
13
13
|
export { DEFAULT_VERDICTS, isJudgeError, semanticRuleSpec } from './verdicts';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { SpaceGraph } from '../space-graph';
|
|
2
2
|
import type { Rule, RuleSeverity, SpaceNode } from '../types';
|
|
3
3
|
import { type TrimReport } from './trim';
|
|
4
|
-
import type { SemanticJudgeProvider } from '
|
|
4
|
+
import type { SemanticJudgeProvider } from './types';
|
|
5
5
|
export type NodeJudgement = {
|
|
6
6
|
rule: Rule;
|
|
7
|
-
/** True when the verdict
|
|
8
|
-
|
|
9
|
-
/** Severity of the
|
|
7
|
+
/** True when the verdict has a severity (so it reports a violation), or the provider errored. */
|
|
8
|
+
violated: boolean;
|
|
9
|
+
/** Severity of the verdict; unset for a judge error, which takes the rule's own severity. */
|
|
10
10
|
severity?: RuleSeverity;
|
|
11
11
|
verdict?: string;
|
|
12
12
|
/** User-facing guidance declared for the failing verdict. */
|