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.
@@ -0,0 +1,171 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { parseDocument } from 'yaml';
4
+ import { runCheckCommand } from './check-command.js';
5
+ import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
6
+ import { compileWorkspace } from './compiler.js';
7
+ import { evaluateEvidenceWorkspace, loadEvidence } from './evidence.js';
8
+ import { loadProjection } from './projection.js';
9
+ import { reconcileEvidenceReports, } from './reconciliation.js';
10
+ import { loadWorkspaceManifest } from './workspace.js';
11
+ const plural = (count, singular, pluralForm) => `${count} ${count === 1 ? singular : (pluralForm ?? `${singular}s`)}`;
12
+ export function runStatusCommand(options, cwd) {
13
+ const json = options.includes('--json');
14
+ const paths = options.filter((option) => option !== '--json');
15
+ const [workspacePath] = paths;
16
+ if (paths.length !== 1 ||
17
+ workspacePath === undefined ||
18
+ workspacePath.startsWith('-')) {
19
+ return { exitCode: 2, stdout: '', stderr: usage };
20
+ }
21
+ try {
22
+ const manifestSource = readFileSync(resolve(cwd, workspacePath), 'utf8');
23
+ if (parseDocument(manifestSource).get('format') !==
24
+ 'yarramate/workspace/v1') {
25
+ return {
26
+ exitCode: 2,
27
+ stdout: '',
28
+ stderr: 'status requires an explicit workspace manifest (yarramate/workspace/v1)\n',
29
+ };
30
+ }
31
+ const loadedWorkspace = loadWorkspaceManifest({ path: workspacePath, source: manifestSource }, cwd);
32
+ if (!loadedWorkspace.ok) {
33
+ return {
34
+ exitCode: 1,
35
+ stdout: json
36
+ ? diagnosticJson(loadedWorkspace.diagnostics)
37
+ : humanDiagnostics(loadedWorkspace.diagnostics),
38
+ stderr: '',
39
+ };
40
+ }
41
+ const workspace = loadedWorkspace.workspace;
42
+ const checked = runCheckCommand([workspacePath, '--json'], cwd);
43
+ const checkPayload = JSON.parse(checked.stdout);
44
+ const projections = workspace.projections.map((path) => {
45
+ const loaded = loadProjection({
46
+ path,
47
+ source: readFileSync(resolve(cwd, path), 'utf8'),
48
+ });
49
+ return loaded.ok
50
+ ? {
51
+ id: loaded.projection.id,
52
+ path,
53
+ ...(loaded.projection.presentation?.title === undefined
54
+ ? {}
55
+ : { title: loaded.projection.presentation.title }),
56
+ }
57
+ : { id: path, path };
58
+ });
59
+ let documents = workspace.documents.map((path) => ({ id: path, path }));
60
+ let states = [];
61
+ let reconciliation;
62
+ if (checkPayload.ok) {
63
+ const compilation = compileWorkspace([...workspace.profiles, ...workspace.documents].map((path) => ({
64
+ path,
65
+ source: readFileSync(resolve(cwd, path), 'utf8'),
66
+ })));
67
+ if (compilation.ok) {
68
+ const sourceByDocument = new Map(compilation.graph.documents.map((document) => [
69
+ document.id,
70
+ document.source,
71
+ ]));
72
+ documents = workspace.documents.map((path) => {
73
+ const id = [...sourceByDocument.entries()].find(([, source]) => source === path)?.[0];
74
+ return { id: id ?? path, path };
75
+ });
76
+ states = compilation.graph.claims
77
+ .filter(({ predicate }) => predicate === 'yarramate/state/type')
78
+ .map(({ subject, object }) => ({
79
+ id: subject,
80
+ type: 'value' in object && typeof object.value === 'string'
81
+ ? object.value
82
+ : 'baseline',
83
+ }));
84
+ if (workspace.evidence.length > 0) {
85
+ const evidenceDocuments = workspace.evidence.flatMap((path) => {
86
+ const loaded = loadEvidence({
87
+ path,
88
+ source: readFileSync(resolve(cwd, path), 'utf8'),
89
+ });
90
+ return loaded.ok ? [loaded.evidence] : [];
91
+ });
92
+ const evaluation = evaluateEvidenceWorkspace(compilation.graph, evidenceDocuments);
93
+ if (evaluation.ok) {
94
+ reconciliation = reconcileEvidenceReports(workspace.id, evaluation.reports, compilation.graph).summary;
95
+ }
96
+ }
97
+ }
98
+ }
99
+ const result = {
100
+ format: 'yarramate/status-result/v1',
101
+ workspace: workspace.id,
102
+ ok: checkPayload.ok,
103
+ check: {
104
+ ok: checkPayload.ok,
105
+ diagnostics: checkPayload.ok ? [] : checkPayload.diagnostics,
106
+ ...(checkPayload.counted === undefined
107
+ ? {}
108
+ : { counted: checkPayload.counted }),
109
+ },
110
+ ...(reconciliation === undefined ? {} : { reconciliation }),
111
+ inventory: {
112
+ documents,
113
+ profiles: workspace.profiles,
114
+ states,
115
+ projections,
116
+ evidence: workspace.evidence,
117
+ adapterMappings: workspace.adapterMappings,
118
+ contracts: workspace.contracts,
119
+ },
120
+ };
121
+ if (json) {
122
+ return {
123
+ exitCode: result.ok ? 0 : 1,
124
+ stdout: `${JSON.stringify(result, null, 2)}\n`,
125
+ stderr: '',
126
+ };
127
+ }
128
+ const lines = [];
129
+ const counted = result.check.counted;
130
+ lines.push(`Workspace ${result.workspace}: check ${result.ok ? 'ok' : 'failing'}` +
131
+ (counted === undefined
132
+ ? ''
133
+ : ` (${plural(counted.concepts, 'concept')}, ` +
134
+ `${plural(counted.relationships, 'relationship')}, ` +
135
+ `${plural(counted.states, 'state')}, ` +
136
+ `${plural(counted.documents, 'document')})`));
137
+ if (!result.ok) {
138
+ lines.push(`Diagnostics: ${plural(result.check.diagnostics.length, 'error')}; run \`yarramate check ${workspacePath}\` for details`);
139
+ }
140
+ if (result.reconciliation !== undefined) {
141
+ lines.push(`Reconciliation: ${plural(result.reconciliation.observations, 'observation')}, ` +
142
+ `${result.reconciliation.confirmed} confirmed, ` +
143
+ `${plural(result.reconciliation.findings, 'finding')}` +
144
+ (result.reconciliation.findings > 0
145
+ ? ` (${result.reconciliation.contradicted} contradicted, ` +
146
+ `${result.reconciliation.unknown} unknown, ` +
147
+ `${result.reconciliation.notObserved} not observed)`
148
+ : ''));
149
+ }
150
+ lines.push(`Documents: ${documents.map(({ id }) => id).join(', ') || 'none'}`);
151
+ if (states.length > 0) {
152
+ lines.push(`States: ${states.map(({ id, type }) => `${id} (${type})`).join(', ')}`);
153
+ }
154
+ lines.push(`Projections: ${projections
155
+ .map(({ id, title }) => title === undefined ? id : `${id} — ${title}`)
156
+ .join('; ') || 'none'}`);
157
+ lines.push(`Profiles: ${workspace.profiles.length} · ` +
158
+ `Evidence: ${workspace.evidence.length} · ` +
159
+ `Adapter mappings: ${workspace.adapterMappings.length} · ` +
160
+ `Contracts: ${workspace.contracts.length}`);
161
+ return {
162
+ exitCode: result.ok ? 0 : 1,
163
+ stdout: `${lines.join('\n')}\n`,
164
+ stderr: '',
165
+ };
166
+ }
167
+ catch (error) {
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ return { exitCode: 2, stdout: '', stderr: `${message}\n` };
170
+ }
171
+ }
@@ -143,3 +143,51 @@ yarramate-graphify observe \
143
143
  Graphify extraction remains a separate installation and operation. The
144
144
  adapter observes only explicitly mapped nodes and never promotes them into
145
145
  canonical architecture.
146
+
147
+ ## MCP server for agent harnesses
148
+
149
+ Harnesses that load MCP servers can connect the bundled read-only adapter:
150
+
151
+ ```json
152
+ {
153
+ "mcpServers": {
154
+ "yarramate": {
155
+ "command": "yarramate-mcp"
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ It exposes `yarramate_status`, `yarramate_check`, `yarramate_reconcile`,
162
+ and `yarramate_context` (projection path or ad-hoc subjects, with an
163
+ optional token budget). Every tool call executes the same stable CLI in
164
+ the server's working directory; nothing mutates native documents, and
165
+ authoring stays with the CLI and Git review.
166
+
167
+ ## Continuous drift signal in CI
168
+
169
+ The repository root ships a composite GitHub Action that checks the
170
+ workspace and reports intent-vs-evidence drift on every pull request:
171
+
172
+ ```yaml
173
+ name: architecture
174
+ on: pull_request
175
+ jobs:
176
+ drift:
177
+ runs-on: ubuntu-latest
178
+ steps:
179
+ - uses: actions/checkout@v4
180
+ - uses: actions/setup-node@v4
181
+ with:
182
+ node-version: 22
183
+ - uses: yarrasys/yarramate@main
184
+ with:
185
+ workspace: .yarramate/workspace.yaml
186
+ ```
187
+
188
+ The job fails on deterministic correctness errors and, by default, when
189
+ reconciliation reports contradicted claims; unknown and not-observed
190
+ findings are reported in the job summary without failing. Set
191
+ `fail-on-contradiction: 'false'` to make the whole signal advisory. The
192
+ action never mutates sources — it runs only the read-only `check` and
193
+ `reconcile` commands, so it is safe as a required check.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -58,6 +58,7 @@
58
58
  "./schema/likec4-kind-mapping": "./schema/yarramate-likec4-kind-mapping.schema.json",
59
59
  "./schema/likec4-check-result": "./schema/yarramate-likec4-check-result.schema.json",
60
60
  "./schema/state-comparison": "./schema/yarramate-state-comparison.schema.json",
61
+ "./schema/status-result": "./schema/yarramate-status-result.schema.json",
61
62
  "./schema/graph-v2": "./schema/yarramate-graph-v2.schema.json",
62
63
  "./schema/workspace": "./schema/yarramate-workspace.schema.json",
63
64
  "./schema/evidence": "./schema/yarramate-evidence.schema.json",
@@ -69,7 +70,8 @@
69
70
  "bin": {
70
71
  "yarramate": "dist/cli.js",
71
72
  "yarramate-likec4": "dist/adapters/likec4-cli.js",
72
- "yarramate-graphify": "dist/adapters/graphify-cli.js"
73
+ "yarramate-graphify": "dist/adapters/graphify-cli.js",
74
+ "yarramate-mcp": "dist/adapters/mcp-cli.js"
73
75
  },
74
76
  "packageManager": "pnpm@11.7.0",
75
77
  "engines": {
@@ -90,11 +92,11 @@
90
92
  "self:contract": "pnpm build && node dist/cli.js context .yarramate/projections/core-contract-foundation.yaml .yarramate/workspace.yaml",
91
93
  "self:evidence": "pnpm build && node dist/cli.js evidence .yarramate/evidence/repository.yaml .yarramate/workspace.yaml",
92
94
  "self:reconcile": "pnpm build && node dist/cli.js reconcile .yarramate/workspace.yaml",
93
- "self:check:likec4": "pnpm build && node dist/adapters/likec4-cli.js check .yarramate/integrations/likec4/project.yaml .yarramate/workspace.yaml",
94
- "self:check:likec4:json": "pnpm build && node dist/adapters/likec4-cli.js check .yarramate/integrations/likec4/project.yaml --json .yarramate/workspace.yaml",
95
- "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",
96
98
  "validate": "pnpm self:export:likec4 && likec4 validate --no-layout .yarramate-out/likec4",
97
- "verify": "pnpm typecheck && pnpm test && pnpm self:check && pnpm validate",
99
+ "verify": "pnpm typecheck && pnpm build && pnpm test && pnpm self:check && pnpm validate",
98
100
  "test": "vitest run",
99
101
  "typecheck": "tsc --noEmit"
100
102
  },
@@ -34,7 +34,11 @@
34
34
  "items": {
35
35
  "type": "object",
36
36
  "additionalProperties": false,
37
- "required": ["id", "schema", "packageExport"],
37
+ "required": [
38
+ "id",
39
+ "schema",
40
+ "packageExport"
41
+ ],
38
42
  "properties": {
39
43
  "id": {
40
44
  "$ref": "#/$defs/formatIdentity"
@@ -55,14 +59,19 @@
55
59
  "items": {
56
60
  "type": "object",
57
61
  "additionalProperties": false,
58
- "required": ["name", "binary"],
62
+ "required": [
63
+ "name",
64
+ "binary"
65
+ ],
59
66
  "properties": {
60
67
  "name": {
61
68
  "enum": [
62
69
  "init",
63
70
  "add",
64
71
  "connect",
72
+ "new",
65
73
  "check",
74
+ "status",
66
75
  "compile",
67
76
  "view",
68
77
  "context",
@@ -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
  },
@@ -0,0 +1,273 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://yarramate.org/schema/status-result/v1",
4
+ "title": "YarraMate workspace status result",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "format",
9
+ "workspace",
10
+ "ok",
11
+ "check",
12
+ "inventory"
13
+ ],
14
+ "properties": {
15
+ "format": {
16
+ "const": "yarramate/status-result/v1"
17
+ },
18
+ "workspace": {
19
+ "type": "string",
20
+ "minLength": 1
21
+ },
22
+ "ok": {
23
+ "type": "boolean"
24
+ },
25
+ "check": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": [
29
+ "ok",
30
+ "diagnostics"
31
+ ],
32
+ "properties": {
33
+ "ok": {
34
+ "type": "boolean"
35
+ },
36
+ "diagnostics": {
37
+ "type": "array",
38
+ "items": {
39
+ "$ref": "#/$defs/diagnostic"
40
+ }
41
+ },
42
+ "counted": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "required": [
46
+ "documents",
47
+ "concepts",
48
+ "relationships",
49
+ "states"
50
+ ],
51
+ "properties": {
52
+ "documents": {
53
+ "type": "integer",
54
+ "minimum": 0
55
+ },
56
+ "concepts": {
57
+ "type": "integer",
58
+ "minimum": 0
59
+ },
60
+ "relationships": {
61
+ "type": "integer",
62
+ "minimum": 0
63
+ },
64
+ "states": {
65
+ "type": "integer",
66
+ "minimum": 0
67
+ }
68
+ }
69
+ }
70
+ }
71
+ },
72
+ "reconciliation": {
73
+ "type": "object",
74
+ "additionalProperties": false,
75
+ "required": [
76
+ "evidenceDocuments",
77
+ "observations",
78
+ "confirmed",
79
+ "findings",
80
+ "contradicted",
81
+ "unknown",
82
+ "notObserved"
83
+ ],
84
+ "properties": {
85
+ "evidenceDocuments": {
86
+ "type": "integer",
87
+ "minimum": 0
88
+ },
89
+ "observations": {
90
+ "type": "integer",
91
+ "minimum": 0
92
+ },
93
+ "confirmed": {
94
+ "type": "integer",
95
+ "minimum": 0
96
+ },
97
+ "findings": {
98
+ "type": "integer",
99
+ "minimum": 0
100
+ },
101
+ "contradicted": {
102
+ "type": "integer",
103
+ "minimum": 0
104
+ },
105
+ "unknown": {
106
+ "type": "integer",
107
+ "minimum": 0
108
+ },
109
+ "notObserved": {
110
+ "type": "integer",
111
+ "minimum": 0
112
+ }
113
+ }
114
+ },
115
+ "inventory": {
116
+ "type": "object",
117
+ "additionalProperties": false,
118
+ "required": [
119
+ "documents",
120
+ "profiles",
121
+ "states",
122
+ "projections",
123
+ "evidence",
124
+ "adapterMappings",
125
+ "contracts"
126
+ ],
127
+ "properties": {
128
+ "documents": {
129
+ "type": "array",
130
+ "items": {
131
+ "type": "object",
132
+ "additionalProperties": false,
133
+ "required": [
134
+ "id",
135
+ "path"
136
+ ],
137
+ "properties": {
138
+ "id": {
139
+ "type": "string",
140
+ "minLength": 1
141
+ },
142
+ "path": {
143
+ "type": "string",
144
+ "minLength": 1
145
+ }
146
+ }
147
+ }
148
+ },
149
+ "profiles": {
150
+ "type": "array",
151
+ "items": {
152
+ "type": "string",
153
+ "minLength": 1
154
+ }
155
+ },
156
+ "states": {
157
+ "type": "array",
158
+ "items": {
159
+ "type": "object",
160
+ "additionalProperties": false,
161
+ "required": [
162
+ "id",
163
+ "type"
164
+ ],
165
+ "properties": {
166
+ "id": {
167
+ "type": "string",
168
+ "minLength": 1
169
+ },
170
+ "type": {
171
+ "enum": [
172
+ "baseline",
173
+ "transition",
174
+ "target"
175
+ ]
176
+ }
177
+ }
178
+ }
179
+ },
180
+ "projections": {
181
+ "type": "array",
182
+ "items": {
183
+ "type": "object",
184
+ "additionalProperties": false,
185
+ "required": [
186
+ "id",
187
+ "path"
188
+ ],
189
+ "properties": {
190
+ "id": {
191
+ "type": "string",
192
+ "minLength": 1
193
+ },
194
+ "path": {
195
+ "type": "string",
196
+ "minLength": 1
197
+ },
198
+ "title": {
199
+ "type": "string",
200
+ "minLength": 1
201
+ }
202
+ }
203
+ }
204
+ },
205
+ "evidence": {
206
+ "type": "array",
207
+ "items": {
208
+ "type": "string",
209
+ "minLength": 1
210
+ }
211
+ },
212
+ "adapterMappings": {
213
+ "type": "array",
214
+ "items": {
215
+ "type": "string",
216
+ "minLength": 1
217
+ }
218
+ },
219
+ "contracts": {
220
+ "type": "array",
221
+ "items": {
222
+ "type": "string",
223
+ "minLength": 1
224
+ }
225
+ }
226
+ }
227
+ }
228
+ },
229
+ "$defs": {
230
+ "diagnostic": {
231
+ "type": "object",
232
+ "additionalProperties": false,
233
+ "required": [
234
+ "severity",
235
+ "code",
236
+ "message",
237
+ "path",
238
+ "pointer",
239
+ "line",
240
+ "column"
241
+ ],
242
+ "properties": {
243
+ "severity": {
244
+ "const": "error"
245
+ },
246
+ "code": {
247
+ "type": "string",
248
+ "pattern": "^YM[0-9]{3}$"
249
+ },
250
+ "message": {
251
+ "type": "string",
252
+ "minLength": 1
253
+ },
254
+ "path": {
255
+ "type": "string",
256
+ "minLength": 1
257
+ },
258
+ "pointer": {
259
+ "type": "string",
260
+ "pattern": "^/"
261
+ },
262
+ "line": {
263
+ "type": "integer",
264
+ "minimum": 1
265
+ },
266
+ "column": {
267
+ "type": "integer",
268
+ "minimum": 1
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }