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.
- package/README.md +52 -198
- package/dist/api.d.ts +17 -15
- package/dist/api.js +12 -9
- package/dist/commands/diagram.d.ts +1 -0
- package/dist/commands/diagram.js +7 -1
- package/dist/commands/docs.js +1 -0
- package/dist/commands/dump.d.ts +15 -2
- package/dist/commands/dump.js +18 -4
- package/dist/commands/providers.d.ts +4 -0
- package/dist/commands/providers.js +52 -0
- package/dist/commands/render.d.ts +4 -0
- package/dist/commands/render.js +64 -9
- package/dist/commands/schemas.d.ts +2 -2
- package/dist/commands/schemas.js +133 -22
- package/dist/commands/show.d.ts +1 -0
- package/dist/commands/show.js +7 -1
- package/dist/commands/spaces.d.ts +3 -1
- package/dist/commands/spaces.js +11 -2
- package/dist/commands/validate-file.d.ts +3 -0
- package/dist/commands/validate-file.js +24 -6
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.js +79 -23
- package/dist/config.d.ts +25 -1
- package/dist/config.js +106 -12
- package/dist/filter/augment-nodes.d.ts +3 -1
- package/dist/filter/augment-nodes.js +12 -7
- package/dist/filter/filter-nodes.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +96 -31
- package/dist/judge/context.d.ts +20 -0
- package/dist/judge/context.js +105 -0
- package/dist/judge/factory.d.ts +44 -0
- package/dist/judge/factory.js +148 -0
- package/dist/judge/index.d.ts +13 -0
- package/dist/judge/index.js +8 -0
- package/dist/judge/judge-node.d.ts +27 -0
- package/dist/judge/judge-node.js +122 -0
- package/dist/judge/prompt.d.ts +10 -0
- package/dist/judge/prompt.js +92 -0
- package/dist/judge/provider/anthropic.d.ts +17 -0
- package/dist/judge/provider/anthropic.js +43 -0
- package/dist/judge/provider/fake.d.ts +26 -0
- package/dist/judge/provider/fake.js +56 -0
- package/dist/judge/provider/jev.d.ts +37 -0
- package/dist/judge/provider/jev.js +169 -0
- package/dist/judge/provider/openai-decisions.d.ts +21 -0
- package/dist/judge/provider/openai-decisions.js +84 -0
- package/dist/judge/provider/openai.d.ts +20 -0
- package/dist/judge/provider/openai.js +53 -0
- package/dist/judge/provider/per-rule.d.ts +10 -0
- package/dist/judge/provider/per-rule.js +18 -0
- package/dist/judge/trim.d.ts +27 -0
- package/dist/judge/trim.js +62 -0
- package/dist/judge/types.d.ts +92 -0
- package/dist/judge/types.js +9 -0
- package/dist/judge/verdicts.d.ts +24 -0
- package/dist/judge/verdicts.js +82 -0
- package/dist/load-space-graph.d.ts +7 -5
- package/dist/load-space-graph.js +19 -8
- package/dist/plugins/loader.js +3 -3
- package/dist/plugins/markdown/extract-content-links.js +8 -29
- package/dist/plugins/markdown/index.js +8 -2
- package/dist/plugins/markdown/parse-embedded.d.ts +8 -0
- package/dist/plugins/markdown/parse-embedded.js +195 -24
- package/dist/plugins/markdown/read-space.js +42 -14
- package/dist/plugins/markdown/render-directory.d.ts +11 -0
- package/dist/plugins/markdown/render-directory.js +130 -0
- package/dist/plugins/markdown/template-sync.js +3 -4
- package/dist/plugins/markdown/util.d.ts +26 -5
- package/dist/plugins/markdown/util.js +56 -15
- package/dist/plugins/mermaid/render-mermaid.js +2 -1
- package/dist/plugins/miro/sync.js +7 -6
- package/dist/plugins/util.d.ts +39 -1
- package/dist/plugins/util.js +139 -0
- package/dist/read/resolve-graph-edges.d.ts +1 -1
- package/dist/read/resolve-graph-edges.js +46 -27
- package/dist/read/wikilink-utils.d.ts +17 -0
- package/dist/read/wikilink-utils.js +40 -0
- package/dist/render/render.d.ts +3 -1
- package/dist/render/render.js +2 -2
- package/dist/schema/evaluate-rule.d.ts +33 -2
- package/dist/schema/evaluate-rule.js +58 -4
- package/dist/schema/gating.d.ts +69 -0
- package/dist/schema/gating.js +253 -0
- package/dist/schema/meta-schemas.d.ts +13 -0
- package/dist/schema/meta-schemas.js +50 -0
- package/dist/schema/metadata-contract.d.ts +952 -183
- package/dist/schema/metadata-contract.js +177 -33
- package/dist/schema/rule-filter.d.ts +12 -0
- package/dist/schema/rule-filter.js +46 -0
- package/dist/schema/rule-severity.d.ts +13 -0
- package/dist/schema/rule-severity.js +33 -0
- package/dist/schema/schema.d.ts +14 -8
- package/dist/schema/schema.js +308 -199
- package/dist/schema/validate-graph.d.ts +2 -1
- package/dist/schema/validate-graph.js +40 -23
- package/dist/schema/validate-rules.d.ts +16 -3
- package/dist/schema/validate-rules.js +91 -29
- package/dist/schemas/_knowledge_wiki.json +24 -24
- package/dist/schemas/_okf.json +172 -0
- package/dist/schemas/_ost_strict.json +17 -8
- package/dist/schemas/_sctx_base.json +3 -3
- package/dist/schemas/_strategy_general.json +38 -38
- package/dist/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
- package/dist/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
- package/dist/schemas/generated/latest/_structured_context_schema_meta.json +400 -0
- package/dist/schemas/knowledge_wiki.json +10 -9
- package/dist/schemas/okf.json +8 -0
- package/dist/schemas/strategy_general.json +15 -14
- package/dist/schemas/strict_ost.json +17 -16
- package/dist/space-context.d.ts +5 -2
- package/dist/space-context.js +9 -4
- package/dist/space-graph.d.ts +9 -7
- package/dist/space-graph.js +7 -5
- package/dist/testing.d.ts +33 -0
- package/dist/testing.js +72 -0
- package/dist/types.d.ts +64 -16
- package/dist/validate.d.ts +62 -9
- package/dist/validate.js +229 -114
- package/docs/concepts.md +65 -9
- package/docs/config.md +78 -1
- package/docs/rules.md +168 -50
- package/docs/schemas.md +152 -52
- package/package.json +17 -11
- package/schemas/_knowledge_wiki.json +24 -24
- package/schemas/_okf.json +172 -0
- package/schemas/_ost_strict.json +17 -8
- package/schemas/_sctx_base.json +3 -3
- package/schemas/_strategy_general.json +38 -38
- package/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
- package/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
- package/schemas/generated/latest/_structured_context_schema_meta.json +400 -0
- package/schemas/knowledge_wiki.json +10 -9
- package/schemas/okf.json +8 -0
- package/schemas/strategy_general.json +15 -14
- package/schemas/strict_ost.json +17 -16
- package/dist/schemas/generated/_structured_context_schema_meta.json +0 -191
- package/schemas/generated/_structured_context_schema_meta.json +0 -191
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import chokidar from 'chokidar';
|
|
3
3
|
import { getConfigSourceFiles } from '../config';
|
|
4
|
+
import { judgeForRun } from '../judge/factory';
|
|
5
|
+
import { resolveSpacePluginSchemas } from '../plugins/util';
|
|
6
|
+
import { parseRuleFilter } from '../schema/rule-filter';
|
|
4
7
|
import { bundledSchemasDir } from '../schema/schema';
|
|
5
|
-
import { formatErrors, validateSpace } from '../validate';
|
|
8
|
+
import { formatErrors, ruleViolationMessage, validateSpace } from '../validate';
|
|
6
9
|
export async function validate(context, options = {}) {
|
|
7
10
|
const { schema, schemaRefRegistry } = context;
|
|
8
|
-
const result = await validateSpace(context
|
|
11
|
+
const result = await validateSpace(context, {
|
|
12
|
+
depth: options.depth,
|
|
13
|
+
includeGated: options.includeGated,
|
|
14
|
+
judge: judgeForRun(context.config, context.space, options.semantic),
|
|
15
|
+
ruleFilter: options.ruleFilter && parseRuleFilter(options.ruleFilter),
|
|
16
|
+
});
|
|
17
|
+
const ruleCounts = { error: 0, warning: 0, info: 0 };
|
|
18
|
+
for (const v of result.ruleViolations)
|
|
19
|
+
ruleCounts[v.severity]++;
|
|
9
20
|
// JSON output mode
|
|
10
21
|
if (options.json) {
|
|
11
22
|
const errorsByFile = {};
|
|
12
|
-
const addError = (file, key, kind, message) => {
|
|
23
|
+
const addError = (file, key, kind, message, severity) => {
|
|
13
24
|
if (!errorsByFile[file])
|
|
14
25
|
errorsByFile[file] = {};
|
|
15
|
-
errorsByFile[file][key] = { kind, message };
|
|
26
|
+
errorsByFile[file][key] = severity ? { kind, message, severity } : { kind, message };
|
|
16
27
|
};
|
|
17
28
|
for (const { file, errors: ajvErrors, nodeData } of result.nodeErrors) {
|
|
18
29
|
const formatted = formatErrors(ajvErrors, schema, schemaRefRegistry, nodeData);
|
|
@@ -30,24 +41,32 @@ export async function validate(context, options = {}) {
|
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
for (const v of result.ruleViolations) {
|
|
33
|
-
if (v.file) {
|
|
34
|
-
addError(v.file, `rule:${v.ruleId}`, 'rule',
|
|
44
|
+
if (v.file && v.severity === 'error') {
|
|
45
|
+
addError(v.file, `rule:${v.ruleId}`, 'rule', ruleViolationMessage(v), v.severity);
|
|
35
46
|
}
|
|
36
47
|
}
|
|
48
|
+
const globalRuleErrorCount = result.ruleViolations.filter((v) => !v.file && v.severity === 'error').length;
|
|
37
49
|
for (const v of result.hierarchyViolations) {
|
|
38
50
|
addError(v.file, `hierarchy:${v.description}`, 'hierarchy', v.description);
|
|
39
51
|
}
|
|
40
52
|
const parseErrorCount = result.parseIssues.filter((i) => i.severity === 'error').length;
|
|
41
|
-
const errorCount = Object.values(errorsByFile).reduce((sum, errs) => sum + Object.keys(errs).length, 0) +
|
|
53
|
+
const errorCount = Object.values(errorsByFile).reduce((sum, errs) => sum + Object.keys(errs).length, 0) +
|
|
54
|
+
parseErrorCount +
|
|
55
|
+
globalRuleErrorCount;
|
|
42
56
|
console.log(JSON.stringify({
|
|
43
57
|
space: context.space.name,
|
|
44
58
|
valid: errorCount === 0,
|
|
45
59
|
validCount: result.validCount,
|
|
46
60
|
errorCount,
|
|
47
61
|
errors: errorsByFile,
|
|
62
|
+
ruleViolations: result.ruleViolations,
|
|
48
63
|
orphanCount: result.orphans.length,
|
|
49
64
|
parseIssues: result.parseIssues,
|
|
50
65
|
unresolvedContentLinks: result.unresolvedContentLinks,
|
|
66
|
+
coverageGates: result.coverageGates,
|
|
67
|
+
contextTrimmed: result.contextTrimmed,
|
|
68
|
+
skippedByDepth: result.skippedByDepth,
|
|
69
|
+
depthLimit: result.depthLimit,
|
|
51
70
|
}, null, 2));
|
|
52
71
|
return errorCount > 0 ? 1 : 0;
|
|
53
72
|
}
|
|
@@ -79,12 +98,19 @@ export async function validate(context, options = {}) {
|
|
|
79
98
|
console.log(fmt(' Schema validation errors', result.nodeErrorCount, true));
|
|
80
99
|
console.log(fmt(' Broken links', result.refErrors.length, true));
|
|
81
100
|
console.log(fmt(' Duplicate keys', result.duplicateErrors.length, true));
|
|
82
|
-
console.log(fmt(' Rule violations',
|
|
101
|
+
console.log(fmt(' Rule violations (error)', ruleCounts.error, true));
|
|
102
|
+
console.log(fmt(' Rule violations (warning)', ruleCounts.warning, true, true));
|
|
103
|
+
console.log(fmt(' Rule violations (info)', ruleCounts.info, true, true));
|
|
83
104
|
console.log(fmt(' Hierarchy violations', result.hierarchyViolations.length, true));
|
|
84
105
|
console.log(fmt(' Orphans (hierarchy nodes - no parent)', result.orphans.length, true, true));
|
|
85
106
|
console.log(fmt(' Unresolved content links', result.unresolvedContentLinks.length, true, true));
|
|
86
107
|
const parseIssueErrorCount = result.parseIssues.filter((i) => i.severity === 'error').length;
|
|
87
108
|
console.log(fmt(' Excluded during parsing', result.parseIssues.length, true, parseIssueErrorCount === 0));
|
|
109
|
+
if (result.depthLimit !== undefined) {
|
|
110
|
+
console.log(fmt(' Skipped by depth limit', result.skippedByDepth.length, true, true));
|
|
111
|
+
}
|
|
112
|
+
const gatedFileCount = result.coverageGates.reduce((sum, g) => sum + g.gatedFiles.length, 0);
|
|
113
|
+
console.log(fmt(' Rule checks gated (cascade)', gatedFileCount, true, true));
|
|
88
114
|
if (result.orphans.length > 0) {
|
|
89
115
|
console.log(`\nOrphans (hierarchy nodes - no parent):`);
|
|
90
116
|
for (const node of result.orphans)
|
|
@@ -107,14 +133,16 @@ export async function validate(context, options = {}) {
|
|
|
107
133
|
if (result.parseIssues.length > 0) {
|
|
108
134
|
console.log(`\nExcluded during parsing:`);
|
|
109
135
|
for (const issue of result.parseIssues) {
|
|
136
|
+
const loc = issue.line !== undefined ? `:${issue.line}${issue.column !== undefined ? `:${issue.column}` : ''}` : '';
|
|
110
137
|
const detail = issue.message ? ` - ${issue.message}` : '';
|
|
111
|
-
console.log(` ${issue.file}: ${issue.severity} - ${issue.type}${detail}`);
|
|
138
|
+
console.log(` ${issue.file}${loc}: ${issue.severity} - ${issue.type}${detail}`);
|
|
112
139
|
}
|
|
113
140
|
}
|
|
114
141
|
if (result.nodeErrors.length > 0) {
|
|
115
142
|
console.log(`\nSchema validation errors:`);
|
|
116
|
-
result.nodeErrors.forEach(({ file, errors, nodeData }) => {
|
|
117
|
-
|
|
143
|
+
result.nodeErrors.forEach(({ file, line, errors, nodeData }) => {
|
|
144
|
+
const loc = line !== undefined ? `:${line}` : '';
|
|
145
|
+
console.log(`\n ${file}${loc}:`);
|
|
118
146
|
const formatted = formatErrors(errors, schema, schemaRefRegistry, nodeData);
|
|
119
147
|
formatted.forEach(({ message }) => {
|
|
120
148
|
console.log(` ${message}`);
|
|
@@ -123,8 +151,9 @@ export async function validate(context, options = {}) {
|
|
|
123
151
|
}
|
|
124
152
|
if (result.refErrors.length > 0) {
|
|
125
153
|
console.log(`\nBroken links:`);
|
|
126
|
-
result.refErrors.forEach(({ file, parent, error }) => {
|
|
127
|
-
|
|
154
|
+
result.refErrors.forEach(({ file, line, parent, error }) => {
|
|
155
|
+
const loc = line !== undefined ? `:${line}` : '';
|
|
156
|
+
console.log(` ${file}${loc}: ${parent} → ${error}`);
|
|
128
157
|
});
|
|
129
158
|
}
|
|
130
159
|
if (result.duplicateErrors.length > 0) {
|
|
@@ -138,16 +167,20 @@ export async function validate(context, options = {}) {
|
|
|
138
167
|
}
|
|
139
168
|
if (result.ruleViolations.length > 0) {
|
|
140
169
|
console.log(`\nRule violations:`);
|
|
141
|
-
const
|
|
142
|
-
for (const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
for (const [category, violations] of byCategory) {
|
|
148
|
-
console.log(` ${category.toUpperCase()} (${violations.length}):`);
|
|
170
|
+
const severityColor = { error: red, warning: yellow, info: reset };
|
|
171
|
+
for (const severity of ['error', 'warning', 'info']) {
|
|
172
|
+
const violations = result.ruleViolations.filter((v) => v.severity === severity);
|
|
173
|
+
if (violations.length === 0)
|
|
174
|
+
continue;
|
|
175
|
+
console.log(` ${severityColor[severity]}${severity.toUpperCase()}${reset} (${violations.length}):`);
|
|
149
176
|
for (const v of violations) {
|
|
150
|
-
console.log(` ${v.file ? `${v.file}: ` : ''}${v.description}`);
|
|
177
|
+
console.log(` ${v.file ? `${v.file}: ` : ''}${v.description} [${v.category}: ${v.ruleId}]`);
|
|
178
|
+
if (v.evidence) {
|
|
179
|
+
console.log(` Evidence: ${v.evidence}`);
|
|
180
|
+
}
|
|
181
|
+
if (v.guidance) {
|
|
182
|
+
console.log(` Guidance: ${v.guidance}`);
|
|
183
|
+
}
|
|
151
184
|
}
|
|
152
185
|
}
|
|
153
186
|
}
|
|
@@ -157,11 +190,28 @@ export async function validate(context, options = {}) {
|
|
|
157
190
|
console.log(` ${v.file}: ${v.description}`);
|
|
158
191
|
}
|
|
159
192
|
}
|
|
193
|
+
if (result.coverageGates.length > 0) {
|
|
194
|
+
console.log(`\nCoverage — rule checks gated by cascade (use --include-gated to evaluate anyway):`);
|
|
195
|
+
for (const gate of result.coverageGates) {
|
|
196
|
+
const cause = gate.reason === 'rule' ? `blocking rule "${gate.ruleId}"` : `${gate.reason} failure`;
|
|
197
|
+
console.log(` ${gate.file} (${gate.nodeTitle}) — ${cause} gated ${gate.gatedFiles.length} node(s):`);
|
|
198
|
+
for (const file of gate.gatedFiles)
|
|
199
|
+
console.log(` ${file}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (result.skippedByDepth.length > 0) {
|
|
203
|
+
console.log(`\nSkipped by depth limit (--depth ${result.depthLimit}):`);
|
|
204
|
+
for (const { file } of result.skippedByDepth)
|
|
205
|
+
console.log(` ${file}`);
|
|
206
|
+
}
|
|
207
|
+
if (result.contextTrimmed.nodeCount > 0) {
|
|
208
|
+
console.log(`\nContext trimmed for ${result.contextTrimmed.nodeCount} node(s) across rules ${result.contextTrimmed.ruleIds.join(', ')}.`);
|
|
209
|
+
}
|
|
160
210
|
console.log(`\n`);
|
|
161
211
|
if (result.nodeErrorCount > 0 ||
|
|
162
212
|
result.refErrors.length > 0 ||
|
|
163
213
|
result.duplicateErrors.length > 0 ||
|
|
164
|
-
|
|
214
|
+
ruleCounts.error > 0 ||
|
|
165
215
|
result.hierarchyViolations.length > 0 ||
|
|
166
216
|
parseIssueErrorCount > 0) {
|
|
167
217
|
return 1;
|
|
@@ -177,6 +227,12 @@ export async function watchValidate(context) {
|
|
|
177
227
|
if (schemaDir !== bundledSchemasDir) {
|
|
178
228
|
schemaDirs.push(schemaDir);
|
|
179
229
|
}
|
|
230
|
+
const pluginSources = resolveSpacePluginSchemas(context.space, context.config, context.configDir);
|
|
231
|
+
for (const ps of pluginSources) {
|
|
232
|
+
if (!schemaDirs.includes(ps.schemasDir)) {
|
|
233
|
+
schemaDirs.push(ps.schemasDir);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
180
236
|
console.log(`👀 Watching for changes...`);
|
|
181
237
|
console.log(` Config files: ${configFiles.join(', ')}`);
|
|
182
238
|
console.log(` Schema dirs: ${schemaDirs.join(', ')}`);
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type JudgeConfig, type ModelProviderConfig } from './judge/types.js';
|
|
1
2
|
export type SpaceConfig = {
|
|
2
3
|
name: string;
|
|
3
4
|
path: string;
|
|
@@ -8,6 +9,8 @@ export type SpaceConfig = {
|
|
|
8
9
|
views?: Record<string, {
|
|
9
10
|
expression: string;
|
|
10
11
|
}>;
|
|
12
|
+
/** Model provider for this space's semantic rules; overrides the top-level `semantic`. */
|
|
13
|
+
semantic?: JudgeConfig;
|
|
11
14
|
/**
|
|
12
15
|
* Directory of the config file that defined this space, used to anchor relative plugin-config
|
|
13
16
|
* paths. Set by loadConfig; absent for a hand-assembled Config, whose callers must pass an
|
|
@@ -19,6 +22,10 @@ export type Config = {
|
|
|
19
22
|
spaces: SpaceConfig[];
|
|
20
23
|
schema?: string;
|
|
21
24
|
includeSpacesFrom?: string[];
|
|
25
|
+
/** Model providers by name: how to reach each model. */
|
|
26
|
+
modelProviders?: Record<string, ModelProviderConfig>;
|
|
27
|
+
/** Model provider for semantic rules (a provider name). A space's `semantic` overrides it. */
|
|
28
|
+
semantic?: JudgeConfig;
|
|
22
29
|
};
|
|
23
30
|
/** Override the config file path used by loadConfig/updateSpaceField. */
|
|
24
31
|
export declare function setConfigPath(path: string | undefined): void;
|
|
@@ -28,7 +35,24 @@ export declare function configPath(): string;
|
|
|
28
35
|
export declare function loadConfig(): Config;
|
|
29
36
|
/** Get the full space config entry by name. Throws if not found. */
|
|
30
37
|
export declare function getSpaceConfig(name: string, config: Config): SpaceConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a schema identifier or path to an absolute file path.
|
|
40
|
+
*
|
|
41
|
+
* Precedence:
|
|
42
|
+
* 1. Absolute path or URL → used directly.
|
|
43
|
+
* 2. Explicit relative path (./... or ../...) → resolved relative to configDir.
|
|
44
|
+
* 3. Plugin-qualified path (e.g. "sctx-wardley-mapping/wardley_map.json" or "wardley-mapping/wardley_map.json"):
|
|
45
|
+
* resolved against the matching loaded plugin's schemasDir.
|
|
46
|
+
* 4. Simple filename without directory (e.g. "wardley_map.json" or "strategy_general.json"):
|
|
47
|
+
* - configDir/filename if it exists
|
|
48
|
+
* - plugin.schemasDir/filename for any loaded plugin with schemasDir
|
|
49
|
+
* - bundledSchemasDir/filename if it exists
|
|
50
|
+
* - fallback: configDir/filename
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveSchemaPath(rawSchema: string | undefined, configDir: string, space?: SpaceConfig, config?: Config): string;
|
|
31
53
|
/** Resolve schema path: CLI arg > space-level config > global config. Throws if none configured. */
|
|
32
|
-
export declare function resolveSchema(config: Config, space?: SpaceConfig
|
|
54
|
+
export declare function resolveSchema(config: Config, space?: SpaceConfig, options?: {
|
|
55
|
+
configDir?: string;
|
|
56
|
+
}): string;
|
|
33
57
|
/** Update a string field on a space entry and persist config. When `plugin` is given, updates that field inside `space.plugins[plugin]` instead. */
|
|
34
58
|
export declare function updateSpaceField(spaceName: string, field: string, value: string, plugin?: string): void;
|
package/dist/config.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
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
|
-
import
|
|
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 {
|
|
8
|
-
import {
|
|
6
|
+
import { ENV_CONFIG_VAR, XDG_CONFIG_DIR } from './constants.js';
|
|
7
|
+
import { MODEL_PROVIDER_APIS } from './judge/types.js';
|
|
8
|
+
import { normalizePluginName, resolvePluginSchemasDir, resolveSpacePluginSchemas, shortenPluginName, } from './plugins/util.js';
|
|
9
|
+
import { bundledSchemasDir } from './schema/schema.js';
|
|
10
|
+
const MODEL_PROVIDER_SCHEMA = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
api: { type: 'string', enum: MODEL_PROVIDER_APIS },
|
|
14
|
+
model: { type: 'string' },
|
|
15
|
+
modelEnv: { type: 'string' },
|
|
16
|
+
endpoint: { type: 'string' },
|
|
17
|
+
endpointEnv: { type: 'string' },
|
|
18
|
+
apiKey: { type: 'string' },
|
|
19
|
+
apiKeyEnv: { type: 'string' },
|
|
20
|
+
},
|
|
21
|
+
required: ['api'],
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
};
|
|
24
|
+
const SEMANTIC_CONFIG_SCHEMA = {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: { provider: { type: 'string', minLength: 1 } },
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
};
|
|
9
29
|
const CONFIG_SCHEMA = {
|
|
10
30
|
type: 'object',
|
|
11
31
|
properties: {
|
|
@@ -27,6 +47,7 @@ const CONFIG_SCHEMA = {
|
|
|
27
47
|
additionalProperties: false,
|
|
28
48
|
},
|
|
29
49
|
},
|
|
50
|
+
semantic: SEMANTIC_CONFIG_SCHEMA,
|
|
30
51
|
},
|
|
31
52
|
required: ['name', 'path'],
|
|
32
53
|
additionalProperties: false,
|
|
@@ -34,6 +55,8 @@ const CONFIG_SCHEMA = {
|
|
|
34
55
|
},
|
|
35
56
|
schema: { type: 'string' },
|
|
36
57
|
includeSpacesFrom: { type: 'array', items: { type: 'string' } },
|
|
58
|
+
modelProviders: { type: 'object', additionalProperties: MODEL_PROVIDER_SCHEMA },
|
|
59
|
+
semantic: SEMANTIC_CONFIG_SCHEMA,
|
|
37
60
|
},
|
|
38
61
|
anyOf: [{ required: ['spaces'] }, { required: ['includeSpacesFrom'] }],
|
|
39
62
|
additionalProperties: false,
|
|
@@ -87,13 +110,10 @@ function resolveRelativePaths(config, configDir) {
|
|
|
87
110
|
const relSchema = (p) => {
|
|
88
111
|
if (!p || isAbsolute(p) || isUrl(p))
|
|
89
112
|
return p;
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
if (existsSync(localPath))
|
|
93
|
-
return localPath;
|
|
94
|
-
return join(bundledSchemasDir, p);
|
|
113
|
+
if (p.startsWith('./') || p.startsWith('../')) {
|
|
114
|
+
return resolve(configDir, p);
|
|
95
115
|
}
|
|
96
|
-
return
|
|
116
|
+
return p;
|
|
97
117
|
};
|
|
98
118
|
return {
|
|
99
119
|
...config,
|
|
@@ -111,7 +131,7 @@ function _loadConfig(path) {
|
|
|
111
131
|
throw new Error(`Config file not found: ${path}`);
|
|
112
132
|
}
|
|
113
133
|
const config = JSON5.parse(readFileSync(path, 'utf-8'));
|
|
114
|
-
const ajv = new
|
|
134
|
+
const ajv = new Ajv2020();
|
|
115
135
|
const validate = ajv.compile(CONFIG_SCHEMA);
|
|
116
136
|
if (!validate(config)) {
|
|
117
137
|
throw new Error(`Invalid config in ${path}: ${JSON.stringify(validate.errors)}`);
|
|
@@ -162,12 +182,86 @@ export function getSpaceConfig(name, config) {
|
|
|
162
182
|
}
|
|
163
183
|
return space;
|
|
164
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Resolve a schema identifier or path to an absolute file path.
|
|
187
|
+
*
|
|
188
|
+
* Precedence:
|
|
189
|
+
* 1. Absolute path or URL → used directly.
|
|
190
|
+
* 2. Explicit relative path (./... or ../...) → resolved relative to configDir.
|
|
191
|
+
* 3. Plugin-qualified path (e.g. "sctx-wardley-mapping/wardley_map.json" or "wardley-mapping/wardley_map.json"):
|
|
192
|
+
* resolved against the matching loaded plugin's schemasDir.
|
|
193
|
+
* 4. Simple filename without directory (e.g. "wardley_map.json" or "strategy_general.json"):
|
|
194
|
+
* - configDir/filename if it exists
|
|
195
|
+
* - plugin.schemasDir/filename for any loaded plugin with schemasDir
|
|
196
|
+
* - bundledSchemasDir/filename if it exists
|
|
197
|
+
* - fallback: configDir/filename
|
|
198
|
+
*/
|
|
199
|
+
export function resolveSchemaPath(rawSchema, configDir, space, config) {
|
|
200
|
+
if (!rawSchema) {
|
|
201
|
+
throw new Error('No schema configured. Set "schema" in the space config or at the top level of the config file.');
|
|
202
|
+
}
|
|
203
|
+
if (isAbsolute(rawSchema) || isUrl(rawSchema)) {
|
|
204
|
+
return rawSchema;
|
|
205
|
+
}
|
|
206
|
+
if (rawSchema.startsWith('./') || rawSchema.startsWith('../')) {
|
|
207
|
+
return resolve(configDir, rawSchema);
|
|
208
|
+
}
|
|
209
|
+
if (rawSchema.includes('/') || rawSchema.includes('\\')) {
|
|
210
|
+
const normalizedSlash = rawSchema.replace(/\\/g, '/');
|
|
211
|
+
const slashIdx = normalizedSlash.indexOf('/');
|
|
212
|
+
const prefix = normalizedSlash.slice(0, slashIdx);
|
|
213
|
+
const subPath = normalizedSlash.slice(slashIdx + 1);
|
|
214
|
+
const pluginSchemasDir = resolvePluginSchemasDir(prefix, configDir);
|
|
215
|
+
if (pluginSchemasDir) {
|
|
216
|
+
const candidate = resolve(pluginSchemasDir, subPath);
|
|
217
|
+
if (existsSync(candidate)) {
|
|
218
|
+
return candidate;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Check if relative to configDir exists
|
|
222
|
+
const localCandidate = resolve(configDir, rawSchema);
|
|
223
|
+
if (existsSync(localCandidate)) {
|
|
224
|
+
return localCandidate;
|
|
225
|
+
}
|
|
226
|
+
// If plugin schemas dir was found, return candidate under it
|
|
227
|
+
if (pluginSchemasDir) {
|
|
228
|
+
return resolve(pluginSchemasDir, subPath);
|
|
229
|
+
}
|
|
230
|
+
return localCandidate;
|
|
231
|
+
}
|
|
232
|
+
// Simple filename without directory:
|
|
233
|
+
// 1. configDir/filename
|
|
234
|
+
const localCandidate = resolve(configDir, rawSchema);
|
|
235
|
+
if (existsSync(localCandidate)) {
|
|
236
|
+
return localCandidate;
|
|
237
|
+
}
|
|
238
|
+
// 2. Plugin schemas from declared space / config plugins
|
|
239
|
+
if (space || config) {
|
|
240
|
+
const pluginSources = resolveSpacePluginSchemas(space, config ?? { spaces: [] }, configDir);
|
|
241
|
+
for (const source of pluginSources) {
|
|
242
|
+
const pluginCandidate = resolve(source.schemasDir, rawSchema);
|
|
243
|
+
if (existsSync(pluginCandidate)) {
|
|
244
|
+
return pluginCandidate;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// 3. Bundled schemas
|
|
249
|
+
const bundledCandidate = join(bundledSchemasDir, rawSchema);
|
|
250
|
+
if (existsSync(bundledCandidate)) {
|
|
251
|
+
return bundledCandidate;
|
|
252
|
+
}
|
|
253
|
+
return localCandidate;
|
|
254
|
+
}
|
|
165
255
|
/** Resolve schema path: CLI arg > space-level config > global config. Throws if none configured. */
|
|
166
|
-
export function resolveSchema(config, space) {
|
|
256
|
+
export function resolveSchema(config, space, options) {
|
|
167
257
|
const schema = space?.schema ?? config.schema;
|
|
168
258
|
if (!schema) {
|
|
169
259
|
throw new Error('No schema configured. Set "schema" in the space config or at the top level of the config file.');
|
|
170
260
|
}
|
|
261
|
+
const configDir = options?.configDir ?? space?.sourceDir;
|
|
262
|
+
if (configDir) {
|
|
263
|
+
return resolveSchemaPath(schema, configDir, space, config);
|
|
264
|
+
}
|
|
171
265
|
return schema;
|
|
172
266
|
}
|
|
173
267
|
/** Update a string field on a space entry and persist config. When `plugin` is given, updates that field inside `space.plugins[plugin]` instead. */
|
|
@@ -3,6 +3,8 @@ import type { SpaceNode } from '../types';
|
|
|
3
3
|
export type EdgeMetadata = {
|
|
4
4
|
_field: string;
|
|
5
5
|
_source: 'hierarchy' | 'relationship';
|
|
6
|
+
/** Hierarchy name, present only for hierarchy edges. */
|
|
7
|
+
_hierarchy?: string;
|
|
6
8
|
_selfRef: boolean;
|
|
7
9
|
};
|
|
8
10
|
/** A flat node representation augmented with ancestor and descendant traversal arrays. */
|
|
@@ -18,6 +20,6 @@ export type AugmentedFlatNode = Record<string, unknown> & {
|
|
|
18
20
|
*
|
|
19
21
|
* - ancestors: BFS from node via resolvedParents, nearest first, deduplicated by title.
|
|
20
22
|
* - descendants: BFS via childrenIndex, nearest first, deduplicated by title.
|
|
21
|
-
* - Each entry merges the parent/child node's fields with edge metadata (_field, _source, _selfRef).
|
|
23
|
+
* - Each entry merges the parent/child node's fields with edge metadata (_field, _source, _hierarchy, _selfRef).
|
|
22
24
|
*/
|
|
23
25
|
export declare function augmentNode(node: SpaceNode, nodeIndex: ReadonlyMap<string, SpaceNode>, childrenIndex: ReadonlyMap<string, readonly SpaceNode[]>): AugmentedFlatNode;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/** Edge metadata for the resolvedParents entry connecting two nodes. */
|
|
2
|
+
function edgeMetadata(ref) {
|
|
3
|
+
return {
|
|
4
|
+
_field: ref.field,
|
|
5
|
+
_source: ref.source,
|
|
6
|
+
...(ref.hierarchy !== undefined ? { _hierarchy: ref.hierarchy } : {}),
|
|
7
|
+
_selfRef: ref.selfRef,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
1
10
|
/** Flatten a SpaceNode's data fields for use in an augmented representation. */
|
|
2
11
|
function flattenData(node) {
|
|
3
12
|
return {
|
|
@@ -13,7 +22,7 @@ function flattenData(node) {
|
|
|
13
22
|
*
|
|
14
23
|
* - ancestors: BFS from node via resolvedParents, nearest first, deduplicated by title.
|
|
15
24
|
* - descendants: BFS via childrenIndex, nearest first, deduplicated by title.
|
|
16
|
-
* - Each entry merges the parent/child node's fields with edge metadata (_field, _source, _selfRef).
|
|
25
|
+
* - Each entry merges the parent/child node's fields with edge metadata (_field, _source, _hierarchy, _selfRef).
|
|
17
26
|
*/
|
|
18
27
|
export function augmentNode(node, nodeIndex, childrenIndex) {
|
|
19
28
|
const ancestors = buildAncestors(node, nodeIndex);
|
|
@@ -44,9 +53,7 @@ function buildAncestors(node, nodeIndex) {
|
|
|
44
53
|
visited.add(title);
|
|
45
54
|
result.push({
|
|
46
55
|
...flattenData(item.node),
|
|
47
|
-
|
|
48
|
-
_source: item.ref.source,
|
|
49
|
-
_selfRef: item.ref.selfRef,
|
|
56
|
+
...edgeMetadata(item.ref),
|
|
50
57
|
});
|
|
51
58
|
// Continue BFS upward
|
|
52
59
|
for (const ref of item.node.resolvedParents) {
|
|
@@ -78,9 +85,7 @@ function buildDescendants(node, childrenIndex) {
|
|
|
78
85
|
visited.add(title);
|
|
79
86
|
result.push({
|
|
80
87
|
...flattenData(item.childNode),
|
|
81
|
-
|
|
82
|
-
_source: item.ref.source,
|
|
83
|
-
_selfRef: item.ref.selfRef,
|
|
88
|
+
...edgeMetadata(item.ref),
|
|
84
89
|
});
|
|
85
90
|
const grandchildren = childrenIndex.get(title) ?? [];
|
|
86
91
|
for (const grandchild of grandchildren) {
|
package/dist/index.d.ts
CHANGED