yarramate 0.4.0 → 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.
@@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { adapterMappingEntryLocation, adapterMappingLocation, loadAdapterMapping, validateAdapterMapping, } from '../adapter-mapping.js';
5
5
  import { compileWorkspace } from '../compiler.js';
6
- import { diagnosticJson, isMainModule, resolveCliWorkspaceSources, } from '../cli-support.js';
6
+ import { diagnosticJson, isMainModule, resolveCliWorkspaceSources, versionResult, } from '../cli-support.js';
7
7
  import { observeGraphify, } from './graphify.js';
8
8
  const usage = 'Usage:\n' +
9
9
  ' yarramate-graphify observe <graph.json> <mapping.yaml> <workspace-or-source...> --id <evidence-id> --version <major.minor>\n';
@@ -35,6 +35,9 @@ const parseOptions = (options) => {
35
35
  };
36
36
  export function runGraphifyCli(args, cwd = process.cwd()) {
37
37
  const [command, ...options] = args;
38
+ if (command === '--version') {
39
+ return versionResult('yarramate-graphify');
40
+ }
38
41
  const parsed = command === 'observe' ? parseOptions(options) : undefined;
39
42
  if (parsed === undefined ||
40
43
  !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(parsed.id) ||
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
3
- import { resolve } from 'node:path';
3
+ import { dirname, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { createHash, randomUUID } from 'node:crypto';
6
6
  import Ajv2020Module from 'ajv/dist/2020.js';
7
7
  import { isMap, isSeq, parseDocument } from 'yaml';
8
- import { isMainModule, resolveCliWorkspaceSources, } from '../cli-support.js';
8
+ import { isMainModule, resolveCliWorkspaceSources, versionResult, } from '../cli-support.js';
9
9
  import { compileWorkspace } from '../compiler.js';
10
10
  import { adapterMappingLocation, loadAdapterMapping, validateAdapterMapping, } from '../adapter-mapping.js';
11
11
  import { locateSourcePath } from '../source-document.js';
@@ -88,6 +88,33 @@ const checkJson = (ok, diagnostics) => `${JSON.stringify({
88
88
  ok,
89
89
  diagnostics,
90
90
  }, null, 2)}\n`;
91
+ const unmappedSubjectPreviewLength = 3;
92
+ const summarizeUnmappedConcepts = (diagnostics) => {
93
+ const unmapped = diagnostics.filter((diagnostic) => diagnostic.code === 'YMLC102');
94
+ if (unmapped.length <= unmappedSubjectPreviewLength)
95
+ return diagnostics;
96
+ const preview = unmapped
97
+ .slice(0, unmappedSubjectPreviewLength)
98
+ .map((diagnostic) => 'subject' in diagnostic && diagnostic.subject !== undefined
99
+ ? `"${diagnostic.subject}"`
100
+ : `"${diagnostic.path}:${diagnostic.line}"`)
101
+ .join(', ');
102
+ const summary = {
103
+ ...unmapped[0],
104
+ message: `${unmapped.length} projected concepts have no LikeC4 mapping ` +
105
+ `(first: ${preview}); run "yarramate-likec4 map --sync" to add ` +
106
+ 'the missing mappings',
107
+ };
108
+ let summarized = false;
109
+ return diagnostics.flatMap((diagnostic) => {
110
+ if (diagnostic.code !== 'YMLC102')
111
+ return [diagnostic];
112
+ if (summarized)
113
+ return [];
114
+ summarized = true;
115
+ return [summary];
116
+ });
117
+ };
91
118
  const sameJson = (left, right) => JSON.stringify(left) === JSON.stringify(right);
92
119
  const lowerCamel = (value) => value.replaceAll(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
93
120
  const runLikeC4MapSync = (args, cwd) => {
@@ -371,6 +398,9 @@ const publishGeneratedProject = (cwd, outputDirectory, input) => {
371
398
  };
372
399
  };
373
400
  export function runLikeC4Cli(args, cwd = process.cwd()) {
401
+ if (args[0] === '--version') {
402
+ return versionResult('yarramate-likec4');
403
+ }
374
404
  if (args[0] === 'map') {
375
405
  return runLikeC4MapSync(args.slice(1), cwd);
376
406
  }
@@ -460,7 +490,11 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
460
490
  sourcePaths.some((argument) => argument.startsWith('-'))) {
461
491
  return { exitCode: 2, stdout: '', stderr: usage };
462
492
  }
463
- const diagnosticOutput = (diagnostics) => (json ? checkJson(false, diagnostics) : diagnosticJson(diagnostics));
493
+ const diagnosticOutput = (diagnostics) => json
494
+ ? checkJson(false, diagnostics)
495
+ : diagnosticJson(command === 'check'
496
+ ? summarizeUnmappedConcepts(diagnostics)
497
+ : diagnostics);
464
498
  try {
465
499
  const resolved = resolveCliWorkspaceSources(sourcePaths, cwd);
466
500
  if (!resolved.ok) {
@@ -487,6 +521,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
487
521
  stderr: '',
488
522
  };
489
523
  }
524
+ const projectDirectory = dirname(resolve(cwd, projectionPath));
490
525
  const referencedSources = new Map();
491
526
  const referenceDiagnostics = [];
492
527
  const readProjectReference = (path, label, yamlPath, pointer) => {
@@ -496,7 +531,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
496
531
  try {
497
532
  const source = {
498
533
  path,
499
- source: readFileSync(resolve(cwd, path), 'utf8'),
534
+ source: readFileSync(resolve(projectDirectory, path), 'utf8'),
500
535
  };
501
536
  referencedSources.set(path, source);
502
537
  return source;
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createInterface } from 'node:readline';
3
- import { isMainModule } from '../cli-support.js';
3
+ import { isMainModule, packageVersion, versionResult, } from '../cli-support.js';
4
4
  import { runCli } from '../cli.js';
5
5
  const workspaceProperty = {
6
6
  workspace: {
@@ -97,7 +97,7 @@ export const handleRequest = (request) => {
97
97
  respond(id, {
98
98
  protocolVersion: '2025-06-18',
99
99
  capabilities: { tools: {} },
100
- serverInfo: { name: 'yarramate', version: '0.1' },
100
+ serverInfo: { name: 'yarramate', version: packageVersion },
101
101
  instructions: 'Read-only architecture context for YarraMate workspaces. The native documents in the repository remain canonical; this server never mutates them.',
102
102
  });
103
103
  return;
@@ -142,24 +142,31 @@ export const handleRequest = (request) => {
142
142
  }
143
143
  };
144
144
  if (isMainModule(import.meta.url, process.argv[1])) {
145
- const lines = createInterface({ input: process.stdin });
146
- lines.on('line', (line) => {
147
- const text = line.trim();
148
- if (text.length === 0)
149
- return;
150
- let request;
151
- try {
152
- request = JSON.parse(text);
153
- }
154
- catch {
155
- respondError(null, -32700, 'Parse error');
156
- return;
157
- }
158
- try {
159
- handleRequest(request);
160
- }
161
- catch (error) {
162
- respondError(request.id ?? null, -32603, error instanceof Error ? error.message : String(error));
163
- }
164
- });
145
+ if (process.argv[2] === '--version') {
146
+ const result = versionResult('yarramate-mcp');
147
+ process.stdout.write(result.stdout);
148
+ process.exitCode = result.exitCode;
149
+ }
150
+ else {
151
+ const lines = createInterface({ input: process.stdin });
152
+ lines.on('line', (line) => {
153
+ const text = line.trim();
154
+ if (text.length === 0)
155
+ return;
156
+ let request;
157
+ try {
158
+ request = JSON.parse(text);
159
+ }
160
+ catch {
161
+ respondError(null, -32700, 'Parse error');
162
+ return;
163
+ }
164
+ try {
165
+ handleRequest(request);
166
+ }
167
+ catch (error) {
168
+ respondError(request.id ?? null, -32603, error instanceof Error ? error.message : String(error));
169
+ }
170
+ });
171
+ }
165
172
  }
@@ -5,7 +5,9 @@ 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]\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";
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;
@@ -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,7 +17,13 @@ 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]\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';
18
27
  export const diagnosticJson = (diagnostics) => `${JSON.stringify({
19
28
  format: 'yarramate/diagnostic-result/v1',
20
29
  diagnostics,
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ 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
11
  import { runStatusCommand } from './status-command.js';
@@ -297,7 +297,7 @@ const runReconciliation = (options, cwd) => {
297
297
  }
298
298
  return {
299
299
  exitCode: 0,
300
- stdout: `${JSON.stringify(reconcileEvidenceReports(loadedWorkspace.workspace.id, evaluation.reports), null, 2)}\n`,
300
+ stdout: `${JSON.stringify(reconcileEvidenceReports(loadedWorkspace.workspace.id, evaluation.reports, compilation.graph), null, 2)}\n`,
301
301
  stderr: '',
302
302
  };
303
303
  }
@@ -353,8 +353,10 @@ const runStateComparison = (options, cwd) => {
353
353
  }
354
354
  };
355
355
  const runInit = (options, cwd) => {
356
- const target = options[0];
357
- if (options.length !== 1 ||
356
+ const positional = options.filter((option) => option !== '--no-pointer');
357
+ const writePointer = positional.length === options.length;
358
+ const target = positional[0];
359
+ if (positional.length !== 1 ||
358
360
  target === undefined ||
359
361
  target.startsWith('-')) {
360
362
  return { exitCode: 2, stdout: '', stderr: usage };
@@ -389,10 +391,8 @@ const runInit = (options, cwd) => {
389
391
  'projections: []\n' +
390
392
  'adapterMappings: []\n' +
391
393
  '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` +
394
+ const pointerMarker = '## YarraMate architecture';
395
+ const pointerBlock = `${pointerMarker}\n` +
396
396
  '\n' +
397
397
  'This repository declares its architecture as canonical, versioned\n' +
398
398
  'YarraMate documents in `.yarramate/`. When prose documentation and the\n' +
@@ -403,24 +403,34 @@ const runInit = (options, cwd) => {
403
403
  '- Bounded task context: `yarramate context <projection.yaml> .yarramate/workspace.yaml`\n' +
404
404
  '\n' +
405
405
  '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`;
406
+ // Harnesses look in different files: AGENTS.md is the cross-harness
407
+ // convention, while Claude Code auto-loads CLAUDE.md only. Delivering to
408
+ // both is what makes the pointer reach an agent without instruction.
409
+ const pointerNotes = [];
410
+ if (writePointer) {
411
+ for (const pointerFile of ['AGENTS.md', 'CLAUDE.md']) {
412
+ const pointerPath = resolve(workspaceRoot, pointerFile);
413
+ const displayPointerPath = relative(cwd, pointerPath);
414
+ if (!existsSync(pointerPath)) {
415
+ writeFileSync(pointerPath, pointerBlock, 'utf8');
416
+ pointerNotes.push(`Created ${displayPointerPath} with the YarraMate pointer\n`);
417
+ }
418
+ else {
419
+ const existingPointer = readFileSync(pointerPath, 'utf8');
420
+ if (existingPointer.includes(pointerMarker)) {
421
+ pointerNotes.push(`${displayPointerPath} already declares the YarraMate pointer\n`);
422
+ }
423
+ else {
424
+ writeFileSync(pointerPath, `${existingPointer.replace(/\n*$/, '\n\n')}${pointerBlock}`, 'utf8');
425
+ pointerNotes.push(`Extended ${displayPointerPath} with the YarraMate pointer\n`);
426
+ }
427
+ }
419
428
  }
420
429
  }
421
430
  return {
422
431
  exitCode: 0,
423
- stdout: `Created ${displayPath} and ${displayManifestPath}\n` + agentsNote,
432
+ stdout: `Created ${displayPath} and ${displayManifestPath}\n` +
433
+ pointerNotes.join(''),
424
434
  stderr: '',
425
435
  };
426
436
  };
@@ -706,6 +716,9 @@ export function runCli(args, cwd = process.cwd()) {
706
716
  if (command === '--help' || command === '-h' || command === 'help') {
707
717
  return { exitCode: 0, stdout: usage, stderr: '' };
708
718
  }
719
+ if (command === '--version' || command === '-v') {
720
+ return versionResult('yarramate');
721
+ }
709
722
  if (command === 'init') {
710
723
  return runInit(options, cwd);
711
724
  }
package/dist/compiler.js CHANGED
@@ -54,6 +54,7 @@ function compileWorkspaceResolved(sources) {
54
54
  lineage: [`${coreProfile}#${policy.id}`],
55
55
  sourceAspects: policy.sourceAspects,
56
56
  targetAspects: policy.targetAspects,
57
+ repair: policy.repair,
57
58
  };
58
59
  coreRelationshipKinds.set(policy.id, resolved);
59
60
  relationshipKindByIdentity.set(resolved.identity, resolved);
@@ -798,7 +799,7 @@ function compileWorkspaceResolved(sources) {
798
799
  diagnostics.push({
799
800
  severity: 'error',
800
801
  code: 'YM404',
801
- 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}`}`,
802
803
  path: input.path,
803
804
  pointer,
804
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';
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' },
@@ -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;
@@ -1,4 +1,32 @@
1
- export function reconcileEvidenceReports(workspace, reports) {
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: 'subject' in observation
27
- ? { type: 'subject', id: observation.subject }
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,
@@ -91,7 +91,7 @@ export function runStatusCommand(options, cwd) {
91
91
  });
92
92
  const evaluation = evaluateEvidenceWorkspace(compilation.graph, evidenceDocuments);
93
93
  if (evaluation.ok) {
94
- reconciliation = reconcileEvidenceReports(workspace.id, evaluation.reports).summary;
94
+ reconciliation = reconcileEvidenceReports(workspace.id, evaluation.reports, compilation.graph).summary;
95
95
  }
96
96
  }
97
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -92,11 +92,11 @@
92
92
  "self:contract": "pnpm build && node dist/cli.js context .yarramate/projections/core-contract-foundation.yaml .yarramate/workspace.yaml",
93
93
  "self:evidence": "pnpm build && node dist/cli.js evidence .yarramate/evidence/repository.yaml .yarramate/workspace.yaml",
94
94
  "self:reconcile": "pnpm build && node dist/cli.js reconcile .yarramate/workspace.yaml",
95
- "self:check:likec4": "pnpm build && node dist/adapters/likec4-cli.js check .yarramate/integrations/likec4/project.yaml .yarramate/workspace.yaml",
96
- "self:check:likec4:json": "pnpm build && node dist/adapters/likec4-cli.js check .yarramate/integrations/likec4/project.yaml --json .yarramate/workspace.yaml",
97
- "self:export:likec4": "pnpm build && node dist/adapters/likec4-cli.js export-project .yarramate/integrations/likec4/project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml",
95
+ "self:check:likec4": "pnpm build && node dist/adapters/likec4-cli.js check .yarramate/likec4-project.yaml .yarramate/workspace.yaml",
96
+ "self:check:likec4:json": "pnpm build && node dist/adapters/likec4-cli.js check .yarramate/likec4-project.yaml --json .yarramate/workspace.yaml",
97
+ "self:export:likec4": "pnpm build && node dist/adapters/likec4-cli.js export-project .yarramate/likec4-project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml",
98
98
  "validate": "pnpm self:export:likec4 && likec4 validate --no-layout .yarramate-out/likec4",
99
- "verify": "pnpm typecheck && pnpm test && pnpm self:check && pnpm validate",
99
+ "verify": "pnpm typecheck && pnpm build && pnpm test && pnpm self:check && pnpm validate",
100
100
  "test": "vitest run",
101
101
  "typecheck": "tsc --noEmit"
102
102
  },
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "path": {
43
43
  "type": "string",
44
- "description": "Repository-relative path resolved from the CLI working directory. Parent traversal, absolute paths, and backslashes are rejected.",
44
+ "description": "Path resolved from the project-definition document's directory, matching workspace-manifest semantics. Parent traversal, absolute paths, and backslashes are rejected, so references stay beneath the project document.",
45
45
  "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\\\).+$"
46
46
  },
47
47
  "subjectIdentity": {
@@ -66,6 +66,21 @@
66
66
  "message": { "type": "string", "minLength": 1 }
67
67
  }
68
68
  },
69
+ "subjectIdentity": {
70
+ "type": "string",
71
+ "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*#[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
72
+ },
73
+ "assertedRelationship": {
74
+ "type": "object",
75
+ "additionalProperties": false,
76
+ "required": ["from", "to", "kind"],
77
+ "properties": {
78
+ "from": { "$ref": "#/$defs/subjectIdentity" },
79
+ "to": { "$ref": "#/$defs/subjectIdentity" },
80
+ "kind": { "type": "string", "pattern": "^\\S+$" },
81
+ "name": { "type": "string", "minLength": 1 }
82
+ }
83
+ },
69
84
  "finding": {
70
85
  "type": "object",
71
86
  "additionalProperties": false,
@@ -78,6 +93,7 @@
78
93
  ],
79
94
  "properties": {
80
95
  "target": { "$ref": "#/$defs/target" },
96
+ "asserted": { "$ref": "#/$defs/assertedRelationship" },
81
97
  "result": {
82
98
  "enum": ["contradicted", "unknown", "not-observed"]
83
99
  },
@@ -67,7 +67,7 @@ document, projection, evidence, or architecture-state syntax is needed.
67
67
  7. Add the focused projections needed to answer the
68
68
  repository-orientation question. Add a separate projection for every
69
69
  ordered flow that needs a dynamic view, then include each intended view in
70
- `.yarramate/integrations/likec4/project.yaml`.
70
+ `.yarramate/likec4-project.yaml`.
71
71
  8. Unless the user requested semantic-only output, create the optional LikeC4
72
72
  mapping and project described in the authoring reference. Synchronize the
73
73
  project mapping before every export, then run:
@@ -79,9 +79,9 @@ yarramate evidence .yarramate/evidence/<evidence>.yaml .yarramate/workspace.yaml
79
79
  yarramate reconcile .yarramate/workspace.yaml
80
80
  yarramate context .yarramate/projections/<projection>.yaml .yarramate/workspace.yaml
81
81
  yarramate view .yarramate/projections/<projection>.yaml .yarramate/workspace.yaml
82
- yarramate-likec4 check .yarramate/integrations/likec4/project.yaml --json .yarramate/workspace.yaml
82
+ yarramate-likec4 check .yarramate/likec4-project.yaml --json .yarramate/workspace.yaml
83
83
  yarramate-likec4 map --sync .yarramate/integrations/likec4/subject-mapping.yaml .yarramate/workspace.yaml
84
- yarramate-likec4 export-project .yarramate/integrations/likec4/project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml
84
+ yarramate-likec4 export-project .yarramate/likec4-project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml
85
85
  ```
86
86
 
87
87
  9. Audit rendering coverage before handoff. Answer these as reporting
@@ -114,7 +114,7 @@ yarramate-likec4 export-project .yarramate/integrations/likec4/project.yaml .yar
114
114
  - a bounded target projection for implementation agents.
115
115
  - one focused projection per ordered flow that needs a dynamic view.
116
116
  Include every intended view in
117
- `.yarramate/integrations/likec4/project.yaml`.
117
+ `.yarramate/likec4-project.yaml`.
118
118
  6. Synchronize the project mapping before every export, then run:
119
119
 
120
120
  ```sh
@@ -125,9 +125,9 @@ yarramate context .yarramate/projections/<target>.yaml .yarramate/workspace.yaml
125
125
  yarramate view .yarramate/projections/<target>.yaml .yarramate/workspace.yaml
126
126
  yarramate view .yarramate/projections/<flow>.yaml .yarramate/workspace.yaml
127
127
  yarramate compare <document-id>#<baseline-state> <document-id>#<target-state> .yarramate/workspace.yaml
128
- yarramate-likec4 check .yarramate/integrations/likec4/project.yaml --json .yarramate/workspace.yaml
128
+ yarramate-likec4 check .yarramate/likec4-project.yaml --json .yarramate/workspace.yaml
129
129
  yarramate-likec4 map --sync .yarramate/integrations/likec4/subject-mapping.yaml .yarramate/workspace.yaml
130
- yarramate-likec4 export-project .yarramate/integrations/likec4/project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml
130
+ yarramate-likec4 export-project .yarramate/likec4-project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml
131
131
  ```
132
132
 
133
133
  Skip the two adapter commands only when the user requested semantic-only
@@ -169,7 +169,7 @@ retired concept.
169
169
 
170
170
  ```sh
171
171
  yarramate check .yarramate/workspace.yaml --json
172
- yarramate-likec4 check .yarramate/integrations/likec4/project.yaml --json .yarramate/workspace.yaml
172
+ yarramate-likec4 check .yarramate/likec4-project.yaml --json .yarramate/workspace.yaml
173
173
  ```
174
174
 
175
175
  6. If the adapter check reports intended mapping drift, repair it locally,
@@ -181,8 +181,8 @@ yarramate-likec4 check .yarramate/integrations/likec4/project.yaml --json .yarra
181
181
  yarramate-likec4 map --sync --prune .yarramate/integrations/likec4/subject-mapping.yaml .yarramate/workspace.yaml
182
182
  git diff -- .yarramate
183
183
  yarramate check .yarramate/workspace.yaml --json
184
- yarramate-likec4 check .yarramate/integrations/likec4/project.yaml --json .yarramate/workspace.yaml
185
- yarramate-likec4 export-project .yarramate/integrations/likec4/project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml
184
+ yarramate-likec4 check .yarramate/likec4-project.yaml --json .yarramate/workspace.yaml
185
+ yarramate-likec4 export-project .yarramate/likec4-project.yaml .yarramate-out/likec4 .yarramate/workspace.yaml
186
186
  ```
187
187
 
188
188
  7. Require both configured read-only checks to exit successfully after the
@@ -67,6 +67,71 @@ Use `mode: read|write|read-write|unspecified` only with `access`. Use
67
67
  `content` only with `flow`. Prefer a precise relationship over `association`;
68
68
  use association when no stronger semantic meaning is justified.
69
69
 
70
+ Every concept kind carries an aspect: `motivation`, `active-structure`
71
+ (actors, roles, components, nodes, interfaces), `behavior` (processes,
72
+ functions, interactions, services, events), `passive-structure` (objects,
73
+ data, artifacts, material), or `composite`. Four relationship kinds constrain
74
+ endpoint aspects, and the compiler rejects violations as `YM404`:
75
+
76
+ ```text
77
+ assignment source must be active-structure
78
+ access target must be passive-structure
79
+ influence target must be motivation
80
+ triggering source and target must be behavior
81
+ ```
82
+
83
+ The other kinds accept endpoints of any aspect.
84
+
85
+ ## Invocation chains
86
+
87
+ "User invokes command" and "component invokes component" fail `YM404` when
88
+ written as `triggering` between active-structure elements. Name the invoked
89
+ behavior, assign the performers, and trigger between behaviors:
90
+
91
+ ```yaml
92
+ concepts:
93
+ - id: user
94
+ kind: businessActor
95
+ name: User
96
+ - id: cli
97
+ kind: applicationComponent
98
+ name: CLI
99
+ - id: run-check
100
+ kind: applicationProcess
101
+ name: Run check
102
+ relationships:
103
+ - id: user-starts-run-check
104
+ kind: assignment
105
+ from: user
106
+ to: run-check
107
+ name: User invokes the check command
108
+ - id: cli-performs-run-check
109
+ kind: assignment
110
+ from: cli
111
+ to: run-check
112
+ ```
113
+
114
+ Chain steps with `triggering` only between behavior concepts, for example
115
+ `run-check` triggering a downstream process owned by another component.
116
+
117
+ ## Degrading a blocked kind
118
+
119
+ When aspect policy blocks the kind you want—`triggering` between two
120
+ components is the common case—keep the edge legal with `kind: flow` and carry
121
+ the invocation semantics on the edge's `name` and `description`:
122
+
123
+ ```sh
124
+ yarramate connect .yarramate/architecture/main.yaml \
125
+ --id cli-invokes-engine --kind flow \
126
+ --from cli --to engine \
127
+ --name "invokes" \
128
+ --description "The CLI invokes the engine once per check run"
129
+ ```
130
+
131
+ Both fields compile to claims, so evidence can later confirm or contradict
132
+ the recorded invocation semantics; the degradation loses no reviewable
133
+ information.
134
+
70
135
  ## Ownership and constraints
71
136
 
72
137
  ```yaml
@@ -176,18 +241,21 @@ evidence result.
176
241
 
177
242
  ## LikeC4 project
178
243
 
179
- Keep visualization configuration outside native documents:
244
+ Keep visualization configuration outside native documents. Project `mapping`,
245
+ `kindMapping`, and `views[].projection` paths resolve from the
246
+ project-definition document's directory, so place the definition at or above
247
+ everything it references — `.yarramate/likec4-project.yaml` in this layout:
180
248
 
181
249
  ```yaml
182
250
  format: yarramate/likec4-project/v1
183
251
  id: delivery
184
252
  version: "1.0"
185
253
  title: Delivery architecture
186
- mapping: .yarramate/integrations/likec4/subject-mapping.yaml
254
+ mapping: integrations/likec4/subject-mapping.yaml
187
255
  views:
188
- - projection: .yarramate/projections/delivery-target.yaml
256
+ - projection: projections/delivery-target.yaml
189
257
  - id: submit-order
190
- projection: .yarramate/projections/submit-order.yaml
258
+ projection: projections/submit-order.yaml
191
259
  dynamic:
192
260
  steps:
193
261
  - relationship: delivery#customer-triggers-submit
@@ -237,14 +305,14 @@ yarramate compare <from-state> <to-state> .yarramate/workspace.yaml
237
305
  yarramate evidence <evidence.yaml> .yarramate/workspace.yaml
238
306
  yarramate reconcile .yarramate/workspace.yaml
239
307
  yarramate-likec4 check \
240
- .yarramate/integrations/likec4/project.yaml \
308
+ .yarramate/likec4-project.yaml \
241
309
  --json \
242
310
  .yarramate/workspace.yaml
243
311
  yarramate-likec4 map --sync \
244
312
  .yarramate/integrations/likec4/subject-mapping.yaml \
245
313
  .yarramate/workspace.yaml
246
314
  yarramate-likec4 export-project \
247
- .yarramate/integrations/likec4/project.yaml \
315
+ .yarramate/likec4-project.yaml \
248
316
  .yarramate-out/likec4 \
249
317
  .yarramate/workspace.yaml
250
318
  ```