yarramate 0.4.0 → 0.6.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.
@@ -8,10 +8,23 @@ import { compileWorkspace } from './compiler.js';
8
8
  import { checkCoreContract, loadCoreContract, } from './core-contract.js';
9
9
  import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
10
10
  import { loadProjection } from './projection.js';
11
+ import { reconcileEvidenceReports, } from './reconciliation.js';
11
12
  const Ajv2020 = Ajv2020Module.default;
13
+ const strictFindingMessage = (finding) => {
14
+ const observed = finding.evidence.message === undefined
15
+ ? finding.evidence.uri
16
+ : `${finding.evidence.uri}: ${finding.evidence.message}`;
17
+ const assertion = finding.asserted === undefined
18
+ ? `Evidence contradicts ${finding.target.type} "${finding.target.id}"`
19
+ : `Evidence contradicts claim "${finding.target.id}": the model asserts ` +
20
+ `${finding.asserted.from} -> ${finding.asserted.to} (${finding.asserted.kind})`;
21
+ return (`${assertion}, but provider "${finding.provider}" observed otherwise ` +
22
+ `(${observed}); align the model or the evidence to pass --strict`);
23
+ };
12
24
  export function runCheckCommand(options, cwd) {
13
25
  const json = options.includes('--json');
14
- const paths = options.filter((option) => option !== '--json');
26
+ const strict = options.includes('--strict');
27
+ const paths = options.filter((option) => option !== '--json' && option !== '--strict');
15
28
  const unknownOption = paths.find((path) => path.startsWith('-'));
16
29
  if (unknownOption !== undefined || paths.length === 0) {
17
30
  return { exitCode: 2, stdout: '', stderr: usage };
@@ -171,6 +184,46 @@ export function runCheckCommand(options, cwd) {
171
184
  const diagnostics = result.ok
172
185
  ? optionalDiagnostics
173
186
  : result.diagnostics;
187
+ // Strict only tightens a passing check: base diagnostics already fail
188
+ // the gate, so contradictions are folded in only once everything else
189
+ // holds, and each one is anchored at the claim the model declares.
190
+ const strictEvaluation = strict && result.ok && ok
191
+ ? (() => {
192
+ const reports = evidenceEvaluation !== undefined && evidenceEvaluation.ok
193
+ ? evidenceEvaluation.reports
194
+ : [];
195
+ const graph = result.graph;
196
+ const contradicted = reconcileEvidenceReports('strict', reports, graph).findings.filter(({ result: outcome }) => outcome === 'contradicted');
197
+ return {
198
+ observations: reports.reduce((total, report) => total + report.observations.length, 0),
199
+ diagnostics: sortDiagnostics(contradicted.map((finding) => {
200
+ const anchor = graph.claims.find(({ id }) => id === finding.target.id) ??
201
+ graph.claims.find(({ subject, predicate }) => subject === finding.target.id &&
202
+ predicate === 'yarramate/concept/kind') ??
203
+ graph.claims.find(({ subject }) => subject === finding.target.id);
204
+ return {
205
+ severity: 'error',
206
+ code: 'YM901',
207
+ message: strictFindingMessage(finding),
208
+ path: anchor?.source.path ?? finding.evidenceDocument,
209
+ pointer: anchor?.source.pointer ?? '/',
210
+ line: anchor?.source.line ?? 1,
211
+ column: anchor?.source.column ?? 1,
212
+ };
213
+ })),
214
+ };
215
+ })()
216
+ : undefined;
217
+ const strictSummary = strictEvaluation === undefined
218
+ ? undefined
219
+ : {
220
+ observations: strictEvaluation.observations,
221
+ contradicted: strictEvaluation.diagnostics.length,
222
+ };
223
+ const strictOk = strictEvaluation === undefined
224
+ ? true
225
+ : strictEvaluation.diagnostics.length === 0;
226
+ const finalOk = ok && strictOk;
174
227
  const counted = result.ok
175
228
  ? (() => {
176
229
  const states = new Set(result.graph.claims
@@ -186,8 +239,15 @@ export function runCheckCommand(options, cwd) {
186
239
  : undefined;
187
240
  if (json) {
188
241
  return {
189
- exitCode: ok ? 0 : 1,
190
- stdout: checkResultJson(ok, ok ? [] : diagnostics, ok ? counted : undefined),
242
+ exitCode: finalOk ? 0 : 1,
243
+ stdout: checkResultJson(finalOk, finalOk ? [] : ok ? strictEvaluation.diagnostics : diagnostics, finalOk ? counted : undefined, strictSummary),
244
+ stderr: '',
245
+ };
246
+ }
247
+ if (ok && !strictOk) {
248
+ return {
249
+ exitCode: 1,
250
+ stdout: humanDiagnostics(strictEvaluation.diagnostics),
191
251
  stderr: '',
192
252
  };
193
253
  }
@@ -227,13 +287,19 @@ export function runCheckCommand(options, cwd) {
227
287
  ]
228
288
  : []),
229
289
  ].join(' and ');
290
+ const strictLine = strictSummary === undefined
291
+ ? ''
292
+ : strictSummary.observations === 0
293
+ ? 'Strict: no evidence observations to evaluate\n'
294
+ : `Strict: ${strictSummary.observations} ${strictSummary.observations === 1 ? 'observation' : 'observations'}, 0 contradicted\n`;
230
295
  return {
231
296
  exitCode: 0,
232
297
  stdout: `Checked ${checked} (` +
233
298
  `${successfulCounts.concepts} ${successfulCounts.concepts === 1 ? 'concept' : 'concepts'}, ` +
234
299
  `${successfulCounts.relationships} ${successfulCounts.relationships === 1 ? 'relationship' : 'relationships'}, ` +
235
300
  `${successfulCounts.states} ${successfulCounts.states === 1 ? 'state' : 'states'}` +
236
- '): no errors\n',
301
+ '): no errors\n' +
302
+ strictLine,
237
303
  stderr: '',
238
304
  };
239
305
  }
@@ -5,13 +5,18 @@ export interface CliResult {
5
5
  readonly stderr: string;
6
6
  }
7
7
  export declare const isMainModule: (moduleUrl: string, entrypoint: string | undefined) => boolean;
8
- export declare const usage = "Usage:\n yarramate init <directory>\n yarramate add <document.yaml> --id <id> --kind <kind> --name <name> [--status <status>] [--description <text>] [--owner <ref>] [--constraint <id>=<ref> ...] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate connect <document.yaml> --id <id> --kind <kind> --from <ref> --to <ref> [--name <name>] [--description <text>] [--status <status>] [--mode <mode>] [--content <text>] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate check <source.yaml> [source.yaml ...] [--json]\n yarramate new projection <projection.yaml> --id <id> [--version <v>] [--title <text>] [--description <text>] [--document <id> ...] [--subject <ref> ...] [--kind <qualified-kind> ...] [--relationships <mode>]\n yarramate status <workspace.yaml> [--json]\n yarramate compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate context --subject <document-id>#<local-id> [--subject ...] <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate view <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate compare <from-state> <to-state> <source.yaml> [source.yaml ...]\n yarramate evidence <evidence.yaml> <source.yaml> [source.yaml ...]\n yarramate reconcile <workspace.yaml>\n";
8
+ export declare const packageVersion: string;
9
+ export declare const versionResult: (binary: string) => CliResult;
10
+ export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate add <document.yaml> --id <id> --kind <kind> --name <name> [--status <status>] [--description <text>] [--owner <ref>] [--constraint <id>=<ref> ...] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate connect <document.yaml> --id <id> --kind <kind> --from <ref> --to <ref> [--name <name>] [--description <text>] [--status <status>] [--mode <mode>] [--content <text>] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate new projection <projection.yaml> --id <id> [--version <v>] [--title <text>] [--description <text>] [--document <id> ...] [--subject <ref> ...] [--kind <qualified-kind> ...] [--relationships <mode>]\n yarramate status <workspace.yaml> [--json]\n yarramate next <projection.yaml> <workspace.yaml> [--json]\n yarramate compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate context --subject <document-id>#<local-id> [--subject ...] <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate view <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate compare <from-state> <to-state> <source.yaml> [source.yaml ...]\n yarramate evidence <evidence.yaml> <source.yaml> [source.yaml ...]\n yarramate reconcile <workspace.yaml>\n";
9
11
  export declare const diagnosticJson: (diagnostics: unknown) => string;
10
12
  export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counted?: {
11
13
  readonly documents: number;
12
14
  readonly concepts: number;
13
15
  readonly relationships: number;
14
16
  readonly states: number;
17
+ }, strict?: {
18
+ readonly observations: number;
19
+ readonly contradicted: number;
15
20
  }) => string;
16
21
  export declare const humanDiagnostics: (diagnostics: readonly Pick<Diagnostic, "path" | "line" | "column" | "code" | "message">[]) => string;
17
22
  export declare const sortDiagnostics: <T extends Diagnostic>(diagnostics: readonly T[]) => T[];
@@ -3,6 +3,9 @@ import { resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { parseDocument } from 'yaml';
5
5
  import { loadWorkspaceManifest } from './workspace.js';
6
+ import packageManifest from '../package.json' with {
7
+ type: 'json'
8
+ };
6
9
  export const isMainModule = (moduleUrl, entrypoint) => {
7
10
  if (entrypoint === undefined)
8
11
  return false;
@@ -14,16 +17,23 @@ export const isMainModule = (moduleUrl, entrypoint) => {
14
17
  return false;
15
18
  }
16
19
  };
17
- export const usage = 'Usage:\n yarramate init <directory>\n yarramate add <document.yaml> --id <id> --kind <kind> --name <name> [--status <status>] [--description <text>] [--owner <ref>] [--constraint <id>=<ref> ...] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate connect <document.yaml> --id <id> --kind <kind> --from <ref> --to <ref> [--name <name>] [--description <text>] [--status <status>] [--mode <mode>] [--content <text>] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate check <source.yaml> [source.yaml ...] [--json]\n yarramate new projection <projection.yaml> --id <id> [--version <v>] [--title <text>] [--description <text>] [--document <id> ...] [--subject <ref> ...] [--kind <qualified-kind> ...] [--relationships <mode>]\n yarramate status <workspace.yaml> [--json]\n yarramate compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate context --subject <document-id>#<local-id> [--subject ...] <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate view <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate compare <from-state> <to-state> <source.yaml> [source.yaml ...]\n yarramate evidence <evidence.yaml> <source.yaml> [source.yaml ...]\n yarramate reconcile <workspace.yaml>\n';
20
+ export const packageVersion = packageManifest.version;
21
+ export const versionResult = (binary) => ({
22
+ exitCode: 0,
23
+ stdout: `${binary} ${packageVersion}\n`,
24
+ stderr: '',
25
+ });
26
+ export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate add <document.yaml> --id <id> --kind <kind> --name <name> [--status <status>] [--description <text>] [--owner <ref>] [--constraint <id>=<ref> ...] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate connect <document.yaml> --id <id> --kind <kind> --from <ref> --to <ref> [--name <name>] [--description <text>] [--status <status>] [--mode <mode>] [--content <text>] [--reference <id>=<ref> ...] [--present-in <state-ref> ...] [--source <source.yaml> ...]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate new projection <projection.yaml> --id <id> [--version <v>] [--title <text>] [--description <text>] [--document <id> ...] [--subject <ref> ...] [--kind <qualified-kind> ...] [--relationships <mode>]\n yarramate status <workspace.yaml> [--json]\n yarramate next <projection.yaml> <workspace.yaml> [--json]\n yarramate compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate context --subject <document-id>#<local-id> [--subject ...] <source.yaml> [source.yaml ...] [--budget <tokens>]\n yarramate view <projection.yaml> <source.yaml> [source.yaml ...]\n yarramate compare <from-state> <to-state> <source.yaml> [source.yaml ...]\n yarramate evidence <evidence.yaml> <source.yaml> [source.yaml ...]\n yarramate reconcile <workspace.yaml>\n';
18
27
  export const diagnosticJson = (diagnostics) => `${JSON.stringify({
19
28
  format: 'yarramate/diagnostic-result/v1',
20
29
  diagnostics,
21
30
  }, null, 2)}\n`;
22
- export const checkResultJson = (ok, diagnostics, counted) => `${JSON.stringify({
31
+ export const checkResultJson = (ok, diagnostics, counted, strict) => `${JSON.stringify({
23
32
  format: 'yarramate/check-result/v1',
24
33
  ok,
25
34
  diagnostics,
26
35
  ...(counted === undefined ? {} : { counted }),
36
+ ...(strict === undefined ? {} : { strict }),
27
37
  }, null, 2)}\n`;
28
38
  export const humanDiagnostics = (diagnostics) => diagnostics
29
39
  .map((diagnostic) => `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} error ${diagnostic.code} ${diagnostic.message}\n`)
package/dist/cli.js CHANGED
@@ -5,9 +5,10 @@ import { isSeq, parseDocument } from 'yaml';
5
5
  import { compileWorkspace, compileWorkspaceWithProfileContext, } from './compiler.js';
6
6
  import { compareArchitectureStates } from './architecture-state.js';
7
7
  import { serializeSemanticGraph } from './graph.js';
8
- import { diagnosticJson, humanDiagnostics, isMainModule, resolveCliWorkspaceSources, usage, } from './cli-support.js';
8
+ import { diagnosticJson, humanDiagnostics, isMainModule, resolveCliWorkspaceSources, usage, versionResult, } from './cli-support.js';
9
9
  import { runCheckCommand } from './check-command.js';
10
10
  import { runNewCommand } from './new-command.js';
11
+ import { runNextCommand } from './next-command.js';
11
12
  import { runStatusCommand } from './status-command.js';
12
13
  import { evaluateEvidence, evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
13
14
  import { reconcileEvidenceReports } from './reconciliation.js';
@@ -297,7 +298,7 @@ const runReconciliation = (options, cwd) => {
297
298
  }
298
299
  return {
299
300
  exitCode: 0,
300
- stdout: `${JSON.stringify(reconcileEvidenceReports(loadedWorkspace.workspace.id, evaluation.reports), null, 2)}\n`,
301
+ stdout: `${JSON.stringify(reconcileEvidenceReports(loadedWorkspace.workspace.id, evaluation.reports, compilation.graph), null, 2)}\n`,
301
302
  stderr: '',
302
303
  };
303
304
  }
@@ -353,8 +354,10 @@ const runStateComparison = (options, cwd) => {
353
354
  }
354
355
  };
355
356
  const runInit = (options, cwd) => {
356
- const target = options[0];
357
- if (options.length !== 1 ||
357
+ const positional = options.filter((option) => option !== '--no-pointer');
358
+ const writePointer = positional.length === options.length;
359
+ const target = positional[0];
360
+ if (positional.length !== 1 ||
358
361
  target === undefined ||
359
362
  target.startsWith('-')) {
360
363
  return { exitCode: 2, stdout: '', stderr: usage };
@@ -389,10 +392,8 @@ const runInit = (options, cwd) => {
389
392
  'projections: []\n' +
390
393
  'adapterMappings: []\n' +
391
394
  'evidence: []\n', 'utf8');
392
- const agentsPath = resolve(workspaceRoot, 'AGENTS.md');
393
- const displayAgentsPath = relative(cwd, agentsPath);
394
- const agentsMarker = '## YarraMate architecture';
395
- const agentsBlock = `${agentsMarker}\n` +
395
+ const pointerMarker = '## YarraMate architecture';
396
+ const pointerBlock = `${pointerMarker}\n` +
396
397
  '\n' +
397
398
  'This repository declares its architecture as canonical, versioned\n' +
398
399
  'YarraMate documents in `.yarramate/`. When prose documentation and the\n' +
@@ -403,24 +404,34 @@ const runInit = (options, cwd) => {
403
404
  '- Bounded task context: `yarramate context <projection.yaml> .yarramate/workspace.yaml`\n' +
404
405
  '\n' +
405
406
  'Author native documents only; never edit generated output.\n';
406
- let agentsNote;
407
- if (!existsSync(agentsPath)) {
408
- writeFileSync(agentsPath, agentsBlock, 'utf8');
409
- agentsNote = `Created ${displayAgentsPath} with the YarraMate pointer\n`;
410
- }
411
- else {
412
- const existingAgents = readFileSync(agentsPath, 'utf8');
413
- if (existingAgents.includes(agentsMarker)) {
414
- agentsNote = `${displayAgentsPath} already declares the YarraMate pointer\n`;
415
- }
416
- else {
417
- writeFileSync(agentsPath, `${existingAgents.replace(/\n*$/, '\n\n')}${agentsBlock}`, 'utf8');
418
- agentsNote = `Extended ${displayAgentsPath} with the YarraMate pointer\n`;
407
+ // Harnesses look in different files: AGENTS.md is the cross-harness
408
+ // convention, while Claude Code auto-loads CLAUDE.md only. Delivering to
409
+ // both is what makes the pointer reach an agent without instruction.
410
+ const pointerNotes = [];
411
+ if (writePointer) {
412
+ for (const pointerFile of ['AGENTS.md', 'CLAUDE.md']) {
413
+ const pointerPath = resolve(workspaceRoot, pointerFile);
414
+ const displayPointerPath = relative(cwd, pointerPath);
415
+ if (!existsSync(pointerPath)) {
416
+ writeFileSync(pointerPath, pointerBlock, 'utf8');
417
+ pointerNotes.push(`Created ${displayPointerPath} with the YarraMate pointer\n`);
418
+ }
419
+ else {
420
+ const existingPointer = readFileSync(pointerPath, 'utf8');
421
+ if (existingPointer.includes(pointerMarker)) {
422
+ pointerNotes.push(`${displayPointerPath} already declares the YarraMate pointer\n`);
423
+ }
424
+ else {
425
+ writeFileSync(pointerPath, `${existingPointer.replace(/\n*$/, '\n\n')}${pointerBlock}`, 'utf8');
426
+ pointerNotes.push(`Extended ${displayPointerPath} with the YarraMate pointer\n`);
427
+ }
428
+ }
419
429
  }
420
430
  }
421
431
  return {
422
432
  exitCode: 0,
423
- stdout: `Created ${displayPath} and ${displayManifestPath}\n` + agentsNote,
433
+ stdout: `Created ${displayPath} and ${displayManifestPath}\n` +
434
+ pointerNotes.join(''),
424
435
  stderr: '',
425
436
  };
426
437
  };
@@ -706,6 +717,9 @@ export function runCli(args, cwd = process.cwd()) {
706
717
  if (command === '--help' || command === '-h' || command === 'help') {
707
718
  return { exitCode: 0, stdout: usage, stderr: '' };
708
719
  }
720
+ if (command === '--version' || command === '-v') {
721
+ return versionResult('yarramate');
722
+ }
709
723
  if (command === 'init') {
710
724
  return runInit(options, cwd);
711
725
  }
@@ -744,6 +758,9 @@ export function runCli(args, cwd = process.cwd()) {
744
758
  if (command === 'new') {
745
759
  return runNewCommand(options, cwd);
746
760
  }
761
+ if (command === 'next') {
762
+ return runNextCommand(options, cwd);
763
+ }
747
764
  return { exitCode: 2, stdout: '', stderr: usage };
748
765
  }
749
766
  if (isMainModule(import.meta.url, process.argv[1])) {
package/dist/compiler.js CHANGED
@@ -14,6 +14,23 @@ const validateDocument = new Ajv2020({ allErrors: true }).compile(documentSchema
14
14
  const validateProfile = new Ajv2020({ allErrors: true }).compile(profileSchema);
15
15
  const compareById = (left, right) => left.id.localeCompare(right.id);
16
16
  const presenceClaimId = (subject, state) => `${subject}~present-in-${Buffer.from(state, 'utf8').toString('hex')}`;
17
+ const describeAspect = (aspect) => aspect.replace('-', ' ');
18
+ // Candidate order is the policy-matrix declaration order: the resolved kind
19
+ // map inserts core policies first, then extension kinds as declared.
20
+ const candidateKindHint = (kinds, rejected, sourceAspect, targetAspect) => {
21
+ const candidates = [...kinds]
22
+ .filter(([id, kind]) => id !== rejected &&
23
+ (kind.sourceAspects?.includes(sourceAspect) ?? true) &&
24
+ (kind.targetAspects?.includes(targetAspect) ?? true))
25
+ .map(([id]) => id);
26
+ if (candidates.length === 0) {
27
+ return '';
28
+ }
29
+ const observed = sourceAspect === targetAspect
30
+ ? `both endpoints are ${describeAspect(sourceAspect)}`
31
+ : `source is ${describeAspect(sourceAspect)} and target is ${describeAspect(targetAspect)}`;
32
+ return `; ${observed}; valid candidates: ${candidates.join(', ')}`;
33
+ };
17
34
  const diagnosticFailure = (diagnostics) => ({
18
35
  ok: false,
19
36
  diagnostics: [...diagnostics].sort((left, right) => left.path.localeCompare(right.path) ||
@@ -54,6 +71,7 @@ function compileWorkspaceResolved(sources) {
54
71
  lineage: [`${coreProfile}#${policy.id}`],
55
72
  sourceAspects: policy.sourceAspects,
56
73
  targetAspects: policy.targetAspects,
74
+ repair: policy.repair,
57
75
  };
58
76
  coreRelationshipKinds.set(policy.id, resolved);
59
77
  relationshipKindByIdentity.set(resolved.identity, resolved);
@@ -778,27 +796,35 @@ function compileWorkspaceResolved(sources) {
778
796
  }
779
797
  const policy = selectedProfile.relationshipKinds.get(relationship.kind);
780
798
  if (policy !== undefined) {
781
- for (const endpoint of ['source', 'target']) {
782
- const reference = endpoint === 'source' ? relationship.from : relationship.to;
799
+ const aspectOf = (reference) => {
783
800
  const resolvedConcept = conceptByQualifiedId.get(qualifyReference(value.id, reference));
784
- const kind = resolvedConcept === undefined
801
+ return resolvedConcept === undefined
785
802
  ? undefined
786
803
  : profiles
787
804
  .get(resolvedConcept.profile)
788
- ?.conceptKinds.get(resolvedConcept.concept.kind);
805
+ ?.conceptKinds.get(resolvedConcept.concept.kind)?.aspect;
806
+ };
807
+ const sourceAspect = aspectOf(relationship.from);
808
+ const targetAspect = aspectOf(relationship.to);
809
+ const candidates = sourceAspect === undefined || targetAspect === undefined
810
+ ? ''
811
+ : candidateKindHint(selectedProfile.relationshipKinds, relationship.kind, sourceAspect, targetAspect);
812
+ for (const endpoint of ['source', 'target']) {
813
+ const reference = endpoint === 'source' ? relationship.from : relationship.to;
814
+ const aspect = endpoint === 'source' ? sourceAspect : targetAspect;
789
815
  const allowed = endpoint === 'source'
790
816
  ? policy.sourceAspects
791
817
  : policy.targetAspects;
792
- if (kind !== undefined &&
818
+ if (aspect !== undefined &&
793
819
  allowed !== undefined &&
794
- !allowed.includes(kind.aspect)) {
820
+ !allowed.includes(aspect)) {
795
821
  const field = endpoint === 'source' ? 'from' : 'to';
796
822
  const pointer = `/relationships/${index}/${field}`;
797
823
  const source = location(['relationships', index, field], pointer);
798
824
  diagnostics.push({
799
825
  severity: 'error',
800
826
  code: 'YM404',
801
- message: `Relationship "${relationship.kind}" requires a ${endpoint} with aspect ${allowed.map((aspect) => `"${aspect}"`).join(' or ')}; "${reference}" has aspect "${kind.aspect}"`,
827
+ message: `Relationship "${relationship.kind}" requires a ${endpoint} with aspect ${allowed.map((entry) => `"${entry}"`).join(' or ')}; "${reference}" has aspect "${aspect}"${policy.repair === undefined ? '' : `; ${policy.repair}`}${candidates}`,
802
828
  path: input.path,
803
829
  pointer,
804
830
  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,25 @@
1
+ import { type CliResult } from './cli-support.js';
2
+ interface EvidenceCoverage {
3
+ readonly observations: number;
4
+ readonly confirmed: number;
5
+ readonly contradicted: number;
6
+ readonly unknown: number;
7
+ readonly notObserved: number;
8
+ }
9
+ export interface NextSubject {
10
+ readonly id: string;
11
+ readonly kind: string;
12
+ readonly name?: string;
13
+ readonly dependsOn: readonly string[];
14
+ readonly requiredBy: readonly string[];
15
+ readonly evidence: EvidenceCoverage;
16
+ readonly cycle?: true;
17
+ }
18
+ export interface NextResult {
19
+ readonly format: 'yarramate/next-result/v1';
20
+ readonly workspace: string;
21
+ readonly projection: string;
22
+ readonly subjects: readonly NextSubject[];
23
+ }
24
+ export declare function runNextCommand(options: readonly string[], cwd: string): CliResult;
25
+ export {};