yarramate 0.3.3 → 0.5.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 +1 -0
- package/dist/adapters/graphify-cli.js +4 -1
- package/dist/adapters/likec4-cli.js +39 -4
- package/dist/adapters/mcp-cli.d.ts +9 -0
- package/dist/adapters/mcp-cli.js +172 -0
- package/dist/cli-support.d.ts +3 -1
- package/dist/cli-support.js +10 -1
- package/dist/cli.js +179 -10
- package/dist/compiler.js +17 -5
- package/dist/index.d.ts +1 -1
- package/dist/new-command.d.ts +2 -0
- package/dist/new-command.js +111 -0
- package/dist/profile.d.ts +5 -0
- package/dist/profile.js +4 -0
- package/dist/projection.d.ts +1 -0
- package/dist/projection.js +106 -0
- package/dist/reconciliation.d.ts +9 -1
- package/dist/reconciliation.js +35 -4
- package/dist/source-document.d.ts +3 -1
- package/dist/source-document.js +43 -1
- package/dist/status-command.d.ts +2 -0
- package/dist/status-command.js +171 -0
- package/docs/CONSUMING-YARRAMATE.md +48 -0
- package/package.json +8 -6
- package/schema/yarramate-core-contract.schema.json +11 -2
- package/schema/yarramate-likec4-project.schema.json +1 -1
- package/schema/yarramate-reconciliation-report.schema.json +16 -0
- package/schema/yarramate-status-result.schema.json +273 -0
- package/skills/yarramate-architecture/SKILL.md +18 -9
- package/skills/yarramate-architecture/references/native-authoring.md +74 -6
package/dist/compiler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { LineCounter, parseDocument } from 'yaml';
|
|
3
3
|
import { conceptKinds, relationshipPolicies, } from './profile.js';
|
|
4
|
+
import { closestCandidate, describeSchemaViolation, } from './source-document.js';
|
|
4
5
|
import documentSchema from '../schema/yarramate-document.schema.json' with {
|
|
5
6
|
type: 'json'
|
|
6
7
|
};
|
|
@@ -53,6 +54,7 @@ function compileWorkspaceResolved(sources) {
|
|
|
53
54
|
lineage: [`${coreProfile}#${policy.id}`],
|
|
54
55
|
sourceAspects: policy.sourceAspects,
|
|
55
56
|
targetAspects: policy.targetAspects,
|
|
57
|
+
repair: policy.repair,
|
|
56
58
|
};
|
|
57
59
|
coreRelationshipKinds.set(policy.id, resolved);
|
|
58
60
|
relationshipKindByIdentity.set(resolved.identity, resolved);
|
|
@@ -112,7 +114,7 @@ function compileWorkspaceResolved(sources) {
|
|
|
112
114
|
code: 'YM201',
|
|
113
115
|
message: property
|
|
114
116
|
? `Property "${property}" is not allowed`
|
|
115
|
-
: `Profile schema violation: ${error
|
|
117
|
+
: `Profile schema violation: ${describeSchemaViolation(error)}`,
|
|
116
118
|
path: input.path,
|
|
117
119
|
pointer,
|
|
118
120
|
line: position.line,
|
|
@@ -335,7 +337,7 @@ function compileWorkspaceResolved(sources) {
|
|
|
335
337
|
code: 'YM201',
|
|
336
338
|
message: property
|
|
337
339
|
? `Property "${property}" is not allowed`
|
|
338
|
-
: `Document schema violation: ${error
|
|
340
|
+
: `Document schema violation: ${describeSchemaViolation(error)}`,
|
|
339
341
|
path: input.path,
|
|
340
342
|
pointer,
|
|
341
343
|
line: source.line,
|
|
@@ -536,7 +538,12 @@ function compileWorkspaceResolved(sources) {
|
|
|
536
538
|
diagnostics.push({
|
|
537
539
|
severity: 'error',
|
|
538
540
|
code: 'YM401',
|
|
539
|
-
message: `Unknown concept kind "${concept.kind}" in profile "${value.profile}"
|
|
541
|
+
message: `Unknown concept kind "${concept.kind}" in profile "${value.profile}"${(() => {
|
|
542
|
+
const suggestion = closestCandidate(concept.kind, selectedProfile.conceptKinds.keys());
|
|
543
|
+
return suggestion === undefined
|
|
544
|
+
? ''
|
|
545
|
+
: `; did you mean "${suggestion}"?`;
|
|
546
|
+
})()}`,
|
|
540
547
|
path: input.path,
|
|
541
548
|
pointer,
|
|
542
549
|
line: source.line,
|
|
@@ -742,7 +749,12 @@ function compileWorkspaceResolved(sources) {
|
|
|
742
749
|
diagnostics.push({
|
|
743
750
|
severity: 'error',
|
|
744
751
|
code: 'YM402',
|
|
745
|
-
message: `Unknown relationship kind "${relationship.kind}" in profile "${value.profile}"
|
|
752
|
+
message: `Unknown relationship kind "${relationship.kind}" in profile "${value.profile}"${(() => {
|
|
753
|
+
const suggestion = closestCandidate(relationship.kind, selectedProfile.relationshipKinds.keys());
|
|
754
|
+
return suggestion === undefined
|
|
755
|
+
? ''
|
|
756
|
+
: `; did you mean "${suggestion}"?`;
|
|
757
|
+
})()}`,
|
|
746
758
|
path: input.path,
|
|
747
759
|
pointer,
|
|
748
760
|
line: source.line,
|
|
@@ -787,7 +799,7 @@ function compileWorkspaceResolved(sources) {
|
|
|
787
799
|
diagnostics.push({
|
|
788
800
|
severity: 'error',
|
|
789
801
|
code: 'YM404',
|
|
790
|
-
message: `Relationship "${relationship.kind}" requires a ${endpoint} with aspect ${allowed.map((aspect) => `"${aspect}"`).join(' or ')}; "${reference}" has aspect "${kind.aspect}"`,
|
|
802
|
+
message: `Relationship "${relationship.kind}" requires a ${endpoint} with aspect ${allowed.map((aspect) => `"${aspect}"`).join(' or ')}; "${reference}" has aspect "${kind.aspect}"${policy.repair === undefined ? '' : `; ${policy.repair}`}`,
|
|
791
803
|
path: input.path,
|
|
792
804
|
pointer,
|
|
793
805
|
line: source.line,
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { checkCoreContract, loadCoreContract, type CoreContract, type CoreContra
|
|
|
4
4
|
export { compareArchitectureStates, type StateComparison, type StateComparisonIssue, type StateComparisonResult, } from './architecture-state.js';
|
|
5
5
|
export { loadWorkspaceManifest, type ResolvedWorkspace, type WorkspaceManifest, type WorkspaceManifestResult, } from './workspace.js';
|
|
6
6
|
export { evaluateEvidence, evaluateEvidenceWorkspace, loadEvidence, type EvidenceDocument, type EvidenceEvaluationResult, type EvidenceLoadResult, type EvidenceLocator, type EvidenceObservation, type EvidenceReport, type EvidenceResult, type EvidenceWorkspaceEvaluationResult, } from './evidence.js';
|
|
7
|
-
export { reconcileEvidenceReports, type ReconciliationFinding, type ReconciliationReport, } from './reconciliation.js';
|
|
7
|
+
export { reconcileEvidenceReports, type AssertedRelationship, type ReconciliationFinding, type ReconciliationReport, } from './reconciliation.js';
|
|
8
8
|
export type { CompilationResult, ContextualCompilationResult, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
9
9
|
export { evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
10
10
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, type AdapterMapping, type AdapterMappingLoadResult, type AdapterMappingValidationResult, type AdapterMappingsValidationResult, type AdapterSubjectMapping, } from './adapter-mapping.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { humanDiagnostics, usage } from './cli-support.js';
|
|
4
|
+
import { loadProjection } from './projection.js';
|
|
5
|
+
const singleValueFlags = new Set([
|
|
6
|
+
'--id',
|
|
7
|
+
'--version',
|
|
8
|
+
'--title',
|
|
9
|
+
'--description',
|
|
10
|
+
'--relationships',
|
|
11
|
+
]);
|
|
12
|
+
const repeatableFlags = new Set(['--document', '--subject', '--kind']);
|
|
13
|
+
const yamlText = (value) => /^[A-Za-z0-9][A-Za-z0-9 ._/@#-]*$/.test(value)
|
|
14
|
+
? value
|
|
15
|
+
: JSON.stringify(value);
|
|
16
|
+
export function runNewCommand(options, cwd) {
|
|
17
|
+
const [family, target, ...flags] = options;
|
|
18
|
+
if (family !== 'projection' || target === undefined || target.startsWith('-')) {
|
|
19
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
20
|
+
}
|
|
21
|
+
const single = new Map();
|
|
22
|
+
const repeated = new Map();
|
|
23
|
+
for (let index = 0; index < flags.length; index += 2) {
|
|
24
|
+
const flag = flags[index];
|
|
25
|
+
const value = flags[index + 1];
|
|
26
|
+
if (flag === undefined ||
|
|
27
|
+
value === undefined ||
|
|
28
|
+
value.startsWith('--') ||
|
|
29
|
+
(!singleValueFlags.has(flag) && !repeatableFlags.has(flag))) {
|
|
30
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
31
|
+
}
|
|
32
|
+
if (singleValueFlags.has(flag)) {
|
|
33
|
+
if (single.has(flag)) {
|
|
34
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
35
|
+
}
|
|
36
|
+
single.set(flag, value);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
repeated.set(flag, [...(repeated.get(flag) ?? []), value]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const id = single.get('--id');
|
|
43
|
+
if (id === undefined) {
|
|
44
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
45
|
+
}
|
|
46
|
+
const documents = repeated.get('--document');
|
|
47
|
+
const subjects = repeated.get('--subject');
|
|
48
|
+
const kinds = repeated.get('--kind');
|
|
49
|
+
if (documents === undefined && subjects === undefined && kinds === undefined) {
|
|
50
|
+
return {
|
|
51
|
+
exitCode: 2,
|
|
52
|
+
stdout: '',
|
|
53
|
+
stderr: 'new projection requires at least one selector: --document, --subject, or --kind\n',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const targetPath = resolve(cwd, target);
|
|
57
|
+
if (existsSync(targetPath)) {
|
|
58
|
+
return {
|
|
59
|
+
exitCode: 2,
|
|
60
|
+
stdout: '',
|
|
61
|
+
stderr: `${target} already exists; nothing was changed\n`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const lines = [
|
|
65
|
+
'format: yarramate/projection/v1',
|
|
66
|
+
`id: ${yamlText(id)}`,
|
|
67
|
+
`version: "${single.get('--version') ?? '1.0'}"`,
|
|
68
|
+
'query:',
|
|
69
|
+
];
|
|
70
|
+
const list = (key, values) => {
|
|
71
|
+
if (values === undefined)
|
|
72
|
+
return;
|
|
73
|
+
lines.push(` ${key}:`);
|
|
74
|
+
for (const value of values)
|
|
75
|
+
lines.push(` - ${yamlText(value)}`);
|
|
76
|
+
};
|
|
77
|
+
list('subjects', subjects);
|
|
78
|
+
list('documents', documents);
|
|
79
|
+
list('kinds', kinds);
|
|
80
|
+
const relationships = single.get('--relationships');
|
|
81
|
+
if (relationships !== undefined) {
|
|
82
|
+
lines.push(` relationships: ${yamlText(relationships)}`);
|
|
83
|
+
}
|
|
84
|
+
const title = single.get('--title');
|
|
85
|
+
const description = single.get('--description');
|
|
86
|
+
if (title !== undefined || description !== undefined) {
|
|
87
|
+
lines.push('presentation:');
|
|
88
|
+
if (title !== undefined)
|
|
89
|
+
lines.push(` title: ${yamlText(title)}`);
|
|
90
|
+
if (description !== undefined) {
|
|
91
|
+
lines.push(` description: ${yamlText(description)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const source = `${lines.join('\n')}\n`;
|
|
95
|
+
const loaded = loadProjection({ path: target, source });
|
|
96
|
+
if (!loaded.ok) {
|
|
97
|
+
return {
|
|
98
|
+
exitCode: 1,
|
|
99
|
+
stdout: '',
|
|
100
|
+
stderr: humanDiagnostics(loaded.diagnostics),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
104
|
+
writeFileSync(targetPath, source, 'utf8');
|
|
105
|
+
return {
|
|
106
|
+
exitCode: 0,
|
|
107
|
+
stdout: `Created ${target} (${loaded.projection.id}@${loaded.projection.version})\n` +
|
|
108
|
+
'Include it in the workspace manifest projections list if no glob already covers it\n',
|
|
109
|
+
stderr: '',
|
|
110
|
+
};
|
|
111
|
+
}
|
package/dist/profile.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export interface RelationshipPolicy {
|
|
|
21
21
|
readonly intent: string;
|
|
22
22
|
readonly sourceAspects?: readonly Aspect[];
|
|
23
23
|
readonly targetAspects?: readonly Aspect[];
|
|
24
|
+
/**
|
|
25
|
+
* Repair hint appended to endpoint-aspect diagnostics (YM404). A remedy,
|
|
26
|
+
* never a correction: the compiler still rejects the input.
|
|
27
|
+
*/
|
|
28
|
+
readonly repair?: string;
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
31
|
* Safe, intentionally broad semantic constraints. A future licensed
|
package/dist/profile.js
CHANGED
|
@@ -137,6 +137,7 @@ export const relationshipPolicies = [
|
|
|
137
137
|
id: 'assignment',
|
|
138
138
|
intent: 'Allocate an active structure to behavior or responsibility',
|
|
139
139
|
sourceAspects: ['active-structure'],
|
|
140
|
+
repair: 'assign from an active-structure element (an actor, component, or node), or use "association"',
|
|
140
141
|
},
|
|
141
142
|
{ id: 'realization', intent: 'Fulfil a more abstract concept' },
|
|
142
143
|
{ id: 'serving', intent: 'Make behavior or an interface available' },
|
|
@@ -144,11 +145,13 @@ export const relationshipPolicies = [
|
|
|
144
145
|
id: 'access',
|
|
145
146
|
intent: 'Read, write, create, or use passive structure',
|
|
146
147
|
targetAspects: ['passive-structure'],
|
|
148
|
+
repair: 'point "access" at passive structure (a business object, data object, or artifact), or use "association"',
|
|
147
149
|
},
|
|
148
150
|
{
|
|
149
151
|
id: 'influence',
|
|
150
152
|
intent: 'Affect a motivation concept',
|
|
151
153
|
targetAspects: ['motivation'],
|
|
154
|
+
repair: 'point "influence" at a motivation concept (a goal, requirement, or principle), or use "association"',
|
|
152
155
|
},
|
|
153
156
|
{ id: 'association', intent: 'Relevant connection with no stronger meaning' },
|
|
154
157
|
{
|
|
@@ -156,6 +159,7 @@ export const relationshipPolicies = [
|
|
|
156
159
|
intent: 'Express temporal or causal precedence',
|
|
157
160
|
sourceAspects: ['behavior'],
|
|
158
161
|
targetAspects: ['behavior'],
|
|
162
|
+
repair: 'use "flow" between active-structure elements, or introduce a behavior concept and "assignment"',
|
|
159
163
|
},
|
|
160
164
|
{ id: 'flow', intent: 'Transfer information, value, goods, or material' },
|
|
161
165
|
{ id: 'specialization', intent: 'Express a more specific form' },
|
package/dist/projection.d.ts
CHANGED
|
@@ -40,3 +40,4 @@ export type ProjectionLoadResult = {
|
|
|
40
40
|
export declare function loadProjection(source: WorkspaceSource): ProjectionLoadResult;
|
|
41
41
|
export declare function evaluateProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext): ProjectionResult;
|
|
42
42
|
export declare function renderProjectionMarkdown(result: ProjectionResult): string;
|
|
43
|
+
export declare function renderBudgetedContext(result: ProjectionResult, budgetTokens: number): string;
|
package/dist/projection.js
CHANGED
|
@@ -181,3 +181,109 @@ export function renderProjectionMarkdown(result) {
|
|
|
181
181
|
}
|
|
182
182
|
return `${lines.join('\n')}\n`;
|
|
183
183
|
}
|
|
184
|
+
const estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
185
|
+
export function renderBudgetedContext(result, budgetTokens) {
|
|
186
|
+
const concepts = result.subjects.filter(({ type }) => type === 'concept');
|
|
187
|
+
const relationships = result.subjects.filter(({ type }) => type === 'relationship');
|
|
188
|
+
const title = result.presentation?.title ?? result.projection;
|
|
189
|
+
const header = [
|
|
190
|
+
`context ${result.projection} — ${title}`,
|
|
191
|
+
`subjects: ${concepts.length} concepts, ${relationships.length} relationships`,
|
|
192
|
+
];
|
|
193
|
+
const subjectLines = [];
|
|
194
|
+
for (const concept of concepts) {
|
|
195
|
+
const name = claimValue(result.claims, concept.id, 'yarramate/concept/name');
|
|
196
|
+
const kind = claimValue(result.claims, concept.id, 'yarramate/concept/kind') ??
|
|
197
|
+
'unknown';
|
|
198
|
+
const status = claimValue(result.claims, concept.id, 'yarramate/lifecycle/status');
|
|
199
|
+
subjectLines.push(`- ${concept.id} [${kind.split('#')[1] ?? kind}]` +
|
|
200
|
+
`${name === undefined ? '' : ` ${name}`}` +
|
|
201
|
+
`${status === undefined ? '' : ` (${status})`}`);
|
|
202
|
+
}
|
|
203
|
+
const relationshipLines = [];
|
|
204
|
+
for (const relationship of relationships) {
|
|
205
|
+
const claim = result.claims.find(({ id, object }) => id === relationship.id && 'ref' in object);
|
|
206
|
+
if (claim !== undefined && 'ref' in claim.object) {
|
|
207
|
+
const kind = claim.predicate.split('#')[1] ?? claim.predicate;
|
|
208
|
+
relationshipLines.push(`- ${claim.subject} -${kind}-> ${claim.object.ref}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const descriptionLines = [];
|
|
212
|
+
for (const subject of result.subjects) {
|
|
213
|
+
const description = claimValue(result.claims, subject.id, 'yarramate/concept/description') ??
|
|
214
|
+
claimValue(result.claims, subject.id, 'yarramate/relationship/description');
|
|
215
|
+
if (description !== undefined) {
|
|
216
|
+
descriptionLines.push(`- ${subject.id}: ${description}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const detailPredicates = new Set([
|
|
220
|
+
'yarramate/ownership/owner',
|
|
221
|
+
'yarramate/constraint/requires',
|
|
222
|
+
'yarramate/reference/refers-to',
|
|
223
|
+
'yarramate/access/mode',
|
|
224
|
+
'yarramate/state/present-in',
|
|
225
|
+
]);
|
|
226
|
+
const detailLines = [];
|
|
227
|
+
for (const claim of result.claims) {
|
|
228
|
+
if (!detailPredicates.has(claim.predicate))
|
|
229
|
+
continue;
|
|
230
|
+
const value = 'ref' in claim.object ? claim.object.ref : claim.object.value;
|
|
231
|
+
const predicate = claim.predicate.split('/').pop() ?? claim.predicate;
|
|
232
|
+
detailLines.push(`- ${claim.subject} ${predicate}: ${value}`);
|
|
233
|
+
}
|
|
234
|
+
// Ranked ladder: the two header lines always render; every other line —
|
|
235
|
+
// including the subject skeleton — competes for the remaining budget in
|
|
236
|
+
// priority order, and anything dropped is always announced rather than
|
|
237
|
+
// silently omitted.
|
|
238
|
+
const sections = [
|
|
239
|
+
{ heading: 'relationships:', lines: relationshipLines },
|
|
240
|
+
{ heading: 'descriptions:', lines: descriptionLines },
|
|
241
|
+
{ heading: 'details:', lines: detailLines },
|
|
242
|
+
];
|
|
243
|
+
const rendered = [...header];
|
|
244
|
+
const omitted = [];
|
|
245
|
+
let spent = estimateTokens(rendered.join('\n'));
|
|
246
|
+
let droppedSubjects = 0;
|
|
247
|
+
for (const line of subjectLines) {
|
|
248
|
+
const cost = estimateTokens(line);
|
|
249
|
+
if (spent + cost > budgetTokens) {
|
|
250
|
+
droppedSubjects += 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
rendered.push(line);
|
|
254
|
+
spent += cost;
|
|
255
|
+
}
|
|
256
|
+
if (droppedSubjects > 0) {
|
|
257
|
+
omitted.push(`subjects ${droppedSubjects} omitted`);
|
|
258
|
+
}
|
|
259
|
+
for (const section of sections) {
|
|
260
|
+
if (section.lines.length === 0)
|
|
261
|
+
continue;
|
|
262
|
+
const kept = [section.heading];
|
|
263
|
+
let sectionSpent = estimateTokens(section.heading);
|
|
264
|
+
let dropped = 0;
|
|
265
|
+
for (const line of section.lines) {
|
|
266
|
+
const cost = estimateTokens(line);
|
|
267
|
+
if (spent + sectionSpent + cost > budgetTokens) {
|
|
268
|
+
dropped += 1;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
kept.push(line);
|
|
272
|
+
sectionSpent += cost;
|
|
273
|
+
}
|
|
274
|
+
if (kept.length > 1) {
|
|
275
|
+
rendered.push('', ...kept);
|
|
276
|
+
spent += sectionSpent;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
dropped = section.lines.length;
|
|
280
|
+
}
|
|
281
|
+
if (dropped > 0) {
|
|
282
|
+
omitted.push(`${section.heading.replace(':', '')} ${dropped} omitted`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (omitted.length > 0) {
|
|
286
|
+
rendered.push('', `[budget ${budgetTokens}: ${omitted.join('; ')} — raise --budget or use JSON mode for the complete slice]`);
|
|
287
|
+
}
|
|
288
|
+
return `${rendered.join('\n')}\n`;
|
|
289
|
+
}
|
package/dist/reconciliation.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import type { SemanticGraph } from './compiler.js';
|
|
1
2
|
import type { EvidenceLocator, EvidenceReport, EvidenceResult } from './evidence.js';
|
|
3
|
+
export interface AssertedRelationship {
|
|
4
|
+
readonly from: string;
|
|
5
|
+
readonly to: string;
|
|
6
|
+
readonly kind: string;
|
|
7
|
+
readonly name?: string;
|
|
8
|
+
}
|
|
2
9
|
export interface ReconciliationFinding {
|
|
3
10
|
readonly target: {
|
|
4
11
|
readonly type: 'subject' | 'claim';
|
|
5
12
|
readonly id: string;
|
|
6
13
|
};
|
|
14
|
+
readonly asserted?: AssertedRelationship;
|
|
7
15
|
readonly result: Exclude<EvidenceResult, 'confirmed'>;
|
|
8
16
|
readonly provider: string;
|
|
9
17
|
readonly evidenceDocument: string;
|
|
@@ -23,4 +31,4 @@ export interface ReconciliationReport {
|
|
|
23
31
|
};
|
|
24
32
|
readonly findings: readonly ReconciliationFinding[];
|
|
25
33
|
}
|
|
26
|
-
export declare function reconcileEvidenceReports(workspace: string, reports: readonly EvidenceReport[]): ReconciliationReport;
|
|
34
|
+
export declare function reconcileEvidenceReports(workspace: string, reports: readonly EvidenceReport[], graph?: SemanticGraph): ReconciliationReport;
|
package/dist/reconciliation.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
const assertedRelationshipsByClaim = (graph) => {
|
|
2
|
+
const asserted = new Map();
|
|
3
|
+
if (graph === undefined)
|
|
4
|
+
return asserted;
|
|
5
|
+
const relationshipIds = new Set(graph.subjects
|
|
6
|
+
.filter(({ type }) => type === 'relationship')
|
|
7
|
+
.map(({ id }) => id));
|
|
8
|
+
const names = new Map();
|
|
9
|
+
for (const claim of graph.claims) {
|
|
10
|
+
if (claim.predicate === 'yarramate/relationship/name' &&
|
|
11
|
+
'value' in claim.object) {
|
|
12
|
+
names.set(claim.subject, claim.object.value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
for (const claim of graph.claims) {
|
|
16
|
+
if (!relationshipIds.has(claim.id) || !('ref' in claim.object))
|
|
17
|
+
continue;
|
|
18
|
+
const name = names.get(claim.id);
|
|
19
|
+
asserted.set(claim.id, {
|
|
20
|
+
from: claim.subject,
|
|
21
|
+
to: claim.object.ref,
|
|
22
|
+
kind: claim.predicate,
|
|
23
|
+
...(name === undefined ? {} : { name }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return asserted;
|
|
27
|
+
};
|
|
28
|
+
export function reconcileEvidenceReports(workspace, reports, graph) {
|
|
29
|
+
const assertedByClaim = assertedRelationshipsByClaim(graph);
|
|
2
30
|
const summary = {
|
|
3
31
|
evidenceDocuments: reports.length,
|
|
4
32
|
observations: 0,
|
|
@@ -22,10 +50,13 @@ export function reconcileEvidenceReports(workspace, reports) {
|
|
|
22
50
|
else {
|
|
23
51
|
summary[observation.result] += 1;
|
|
24
52
|
}
|
|
53
|
+
const target = 'subject' in observation
|
|
54
|
+
? { type: 'subject', id: observation.subject }
|
|
55
|
+
: { type: 'claim', id: observation.claim };
|
|
56
|
+
const asserted = target.type === 'claim' ? assertedByClaim.get(target.id) : undefined;
|
|
25
57
|
findings.push({
|
|
26
|
-
target
|
|
27
|
-
|
|
28
|
-
: { type: 'claim', id: observation.claim },
|
|
58
|
+
target,
|
|
59
|
+
...(asserted === undefined ? {} : { asserted }),
|
|
29
60
|
result: observation.result,
|
|
30
61
|
provider: report.provider,
|
|
31
62
|
evidenceDocument: report.evidence,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ValidateFunction } from 'ajv';
|
|
1
|
+
import type { ErrorObject, ValidateFunction } from 'ajv';
|
|
2
2
|
import { LineCounter, parseDocument } from 'yaml';
|
|
3
3
|
import type { Diagnostic, WorkspaceSource } from './compiler.js';
|
|
4
4
|
export interface SourceLocation {
|
|
@@ -20,5 +20,7 @@ export type SourceDocumentResult<T> = {
|
|
|
20
20
|
readonly diagnostics: readonly Diagnostic[];
|
|
21
21
|
};
|
|
22
22
|
export declare const diagnosticOrder: (left: Diagnostic, right: Diagnostic) => number;
|
|
23
|
+
export declare const describeSchemaViolation: (error: Pick<ErrorObject, "keyword" | "message" | "params">) => string;
|
|
24
|
+
export declare const closestCandidate: (value: string, candidates: Iterable<string>) => string | undefined;
|
|
23
25
|
export declare function locateSourcePath(sourcePath: string, yaml: ReturnType<typeof parseDocument>, lineCounter: LineCounter, yamlPath: readonly (string | number)[], pointer: string): SourceLocation;
|
|
24
26
|
export declare function loadSourceDocument<T>(source: WorkspaceSource, validate: ValidateFunction, schemaLabel: string): SourceDocumentResult<T>;
|
package/dist/source-document.js
CHANGED
|
@@ -4,6 +4,48 @@ export const diagnosticOrder = (left, right) => left.path.localeCompare(right.pa
|
|
|
4
4
|
left.column - right.column ||
|
|
5
5
|
left.code.localeCompare(right.code) ||
|
|
6
6
|
left.message.localeCompare(right.message);
|
|
7
|
+
export const describeSchemaViolation = (error) => {
|
|
8
|
+
const base = error.message ?? error.keyword;
|
|
9
|
+
if (error.keyword === 'const') {
|
|
10
|
+
return `${base}: expected ${JSON.stringify(error.params.allowedValue)}`;
|
|
11
|
+
}
|
|
12
|
+
if (error.keyword === 'enum' &&
|
|
13
|
+
Array.isArray(error.params.allowedValues)) {
|
|
14
|
+
const allowed = error.params.allowedValues;
|
|
15
|
+
const shown = allowed
|
|
16
|
+
.slice(0, 8)
|
|
17
|
+
.map((value) => JSON.stringify(value))
|
|
18
|
+
.join(', ');
|
|
19
|
+
return `${base}: ${shown}${allowed.length > 8 ? ', …' : ''}`;
|
|
20
|
+
}
|
|
21
|
+
return base;
|
|
22
|
+
};
|
|
23
|
+
const editDistance = (left, right) => {
|
|
24
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
25
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
26
|
+
let diagonal = previous[0];
|
|
27
|
+
previous[0] = i;
|
|
28
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
29
|
+
const above = previous[j];
|
|
30
|
+
previous[j] = Math.min(above + 1, previous[j - 1] + 1, diagonal + (left[i - 1] === right[j - 1] ? 0 : 1));
|
|
31
|
+
diagonal = above;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return previous[right.length];
|
|
35
|
+
};
|
|
36
|
+
export const closestCandidate = (value, candidates) => {
|
|
37
|
+
const threshold = Math.max(2, Math.floor(value.length / 4));
|
|
38
|
+
let best;
|
|
39
|
+
let bestDistance = threshold + 1;
|
|
40
|
+
for (const candidate of [...candidates].sort()) {
|
|
41
|
+
const distance = editDistance(value.toLowerCase(), candidate.toLowerCase());
|
|
42
|
+
if (distance < bestDistance) {
|
|
43
|
+
best = candidate;
|
|
44
|
+
bestDistance = distance;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return best;
|
|
48
|
+
};
|
|
7
49
|
export function locateSourcePath(sourcePath, yaml, lineCounter, yamlPath, pointer) {
|
|
8
50
|
const node = yaml.getIn(yamlPath, true);
|
|
9
51
|
const offset = typeof node === 'object' &&
|
|
@@ -62,7 +104,7 @@ export function loadSourceDocument(source, validate, schemaLabel) {
|
|
|
62
104
|
code: 'YM201',
|
|
63
105
|
message: property
|
|
64
106
|
? `Property "${property}" is not allowed`
|
|
65
|
-
: `${schemaLabel} schema violation: ${error
|
|
107
|
+
: `${schemaLabel} schema violation: ${describeSchemaViolation(error)}`,
|
|
66
108
|
...location,
|
|
67
109
|
};
|
|
68
110
|
})
|