yarramate 0.1.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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/adapter-mapping.d.ts +40 -0
  4. package/dist/adapter-mapping.js +200 -0
  5. package/dist/adapters/graphify-cli.d.ts +3 -0
  6. package/dist/adapters/graphify-cli.js +127 -0
  7. package/dist/adapters/graphify-entry.d.ts +1 -0
  8. package/dist/adapters/graphify-entry.js +1 -0
  9. package/dist/adapters/graphify.d.ts +23 -0
  10. package/dist/adapters/graphify.js +47 -0
  11. package/dist/adapters/likec4-cli.d.ts +3 -0
  12. package/dist/adapters/likec4-cli.js +662 -0
  13. package/dist/adapters/likec4-export.d.ts +25 -0
  14. package/dist/adapters/likec4-export.js +204 -0
  15. package/dist/adapters/likec4-kind-mapping.d.ts +22 -0
  16. package/dist/adapters/likec4-kind-mapping.js +60 -0
  17. package/dist/adapters/likec4-prepare.d.ts +28 -0
  18. package/dist/adapters/likec4-prepare.js +154 -0
  19. package/dist/adapters/likec4-project.d.ts +58 -0
  20. package/dist/adapters/likec4-project.js +176 -0
  21. package/dist/adapters/likec4.d.ts +4 -0
  22. package/dist/adapters/likec4.js +4 -0
  23. package/dist/architecture-state.d.ts +22 -0
  24. package/dist/architecture-state.js +63 -0
  25. package/dist/check-command.d.ts +2 -0
  26. package/dist/check-command.js +232 -0
  27. package/dist/cli-support.d.ts +24 -0
  28. package/dist/cli-support.js +81 -0
  29. package/dist/cli.d.ts +4 -0
  30. package/dist/cli.js +575 -0
  31. package/dist/compiler.d.ts +66 -0
  32. package/dist/compiler.js +940 -0
  33. package/dist/core-contract.d.ts +43 -0
  34. package/dist/core-contract.js +162 -0
  35. package/dist/evidence.d.ts +58 -0
  36. package/dist/evidence.js +161 -0
  37. package/dist/graph.d.ts +2 -0
  38. package/dist/graph.js +37 -0
  39. package/dist/index.d.ts +11 -0
  40. package/dist/index.js +9 -0
  41. package/dist/profile.d.ts +30 -0
  42. package/dist/profile.js +162 -0
  43. package/dist/projection.d.ts +42 -0
  44. package/dist/projection.js +183 -0
  45. package/dist/reconciliation.d.ts +26 -0
  46. package/dist/reconciliation.js +47 -0
  47. package/dist/source-document.d.ts +24 -0
  48. package/dist/source-document.js +80 -0
  49. package/dist/workspace.d.ts +29 -0
  50. package/dist/workspace.js +114 -0
  51. package/docs/CONSUMING-YARRAMATE.md +145 -0
  52. package/package.json +110 -0
  53. package/schema/yarramate-adapter-mapping.schema.json +59 -0
  54. package/schema/yarramate-check-result.schema.json +92 -0
  55. package/schema/yarramate-core-contract.schema.json +132 -0
  56. package/schema/yarramate-diagnostic-result.schema.json +64 -0
  57. package/schema/yarramate-document.schema.json +168 -0
  58. package/schema/yarramate-evidence-report.schema.json +73 -0
  59. package/schema/yarramate-evidence.schema.json +92 -0
  60. package/schema/yarramate-graph-v2.schema.json +170 -0
  61. package/schema/yarramate-likec4-check-result.schema.json +50 -0
  62. package/schema/yarramate-likec4-diagnostic-result.schema.json +69 -0
  63. package/schema/yarramate-likec4-generated-project-v2.schema.json +100 -0
  64. package/schema/yarramate-likec4-generated-project.schema.json +76 -0
  65. package/schema/yarramate-likec4-kind-mapping.schema.json +65 -0
  66. package/schema/yarramate-likec4-project.schema.json +160 -0
  67. package/schema/yarramate-profile.schema.json +113 -0
  68. package/schema/yarramate-projection-result.schema.json +177 -0
  69. package/schema/yarramate-projection.schema.json +126 -0
  70. package/schema/yarramate-reconciliation-report.schema.json +90 -0
  71. package/schema/yarramate-state-comparison.schema.json +61 -0
  72. package/schema/yarramate-workspace.schema.json +55 -0
  73. package/skills/yarramate-architecture/SKILL.md +135 -0
  74. package/skills/yarramate-architecture/agents/openai.yaml +4 -0
  75. package/skills/yarramate-architecture/references/journey-checklists.md +57 -0
  76. package/skills/yarramate-architecture/references/native-authoring.md +167 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YarraMate contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # YarraMate
2
+
3
+ YarraMate is a tool-neutral semantic architecture engine and guided
4
+ methodology. It turns architectural intent into deterministic, testable
5
+ context shared by people and agents.
6
+
7
+ > YarraMate is pre-release software. Interfaces may evolve before the first
8
+ > stable release.
9
+
10
+ ## Why YarraMate?
11
+
12
+ Architecture documents often drift away from implementation or become tied to
13
+ one notation and tool. YarraMate keeps concise, native YAML documents
14
+ canonical and compiles them into an explicit semantic graph.
15
+
16
+ The same model supports two workflows:
17
+
18
+ - discover an existing project's architecture from repository evidence;
19
+ - design a solution before implementation and later reconcile intent with
20
+ evidence.
21
+
22
+ Git provides authorship, review, history, and acceptance. YarraMate does not
23
+ introduce a parallel governance workflow.
24
+
25
+ ## Product boundaries
26
+
27
+ YarraMate Core:
28
+
29
+ - owns native, versioned architecture documents;
30
+ - compiles a claim-centred, tool-neutral semantic graph;
31
+ - checks deterministic correctness rather than architectural taste;
32
+ - supports explicit workspaces, profiles, projections, evidence, and
33
+ architecture states;
34
+ - exposes a stable CLI for people, CI, skills, and agent harnesses.
35
+
36
+ Optional adapters provide:
37
+
38
+ - LikeC4 visualization from semantic projections;
39
+ - Graphify observations as evidence overlays;
40
+ - separately governed compatibility profiles for external languages.
41
+
42
+ Core does not depend on LikeC4, Graphify, or ArchiMate.
43
+
44
+ YarraMate is not affiliated with or certified by The Open Group. ArchiMate® is
45
+ a registered trademark of The Open Group. LikeC4 and Graphify are independent
46
+ projects; their mention does not imply affiliation or endorsement.
47
+
48
+ ## Repository layout
49
+
50
+ ```text
51
+ src/ compiler, CLI, graph, and adapter sources
52
+ schema/ normative JSON Schemas
53
+ test/ tests and acceptance fixtures
54
+ skills/ portable architecture workflow for agent harnesses
55
+ docs/ contracts, guides, and decisions
56
+ .yarramate/ canonical dogfooded architecture
57
+ .yarramate-out/ reproducible generated output (ignored)
58
+ ```
59
+
60
+ The [documentation index](docs/README.md) links the public guides and
61
+ maintainer material. Start semantic work with the
62
+ [product contract](docs/PRODUCT-CONTRACT.md) and
63
+ [glossary](docs/GLOSSARY.md).
64
+
65
+ ## Development
66
+
67
+ Requirements:
68
+
69
+ - Node.js 22 or newer
70
+ - Corepack
71
+
72
+ ```sh
73
+ corepack enable
74
+ pnpm install --frozen-lockfile
75
+ pnpm run verify
76
+ ```
77
+
78
+ The full CI command runs typechecking, tests, native self-validation, LikeC4
79
+ generation, and LikeC4 validation.
80
+
81
+ Useful focused commands:
82
+
83
+ ```sh
84
+ pnpm build
85
+ pnpm test
86
+ pnpm typecheck
87
+ pnpm self:check
88
+ pnpm self:evidence
89
+ pnpm self:reconcile
90
+ pnpm self:check:likec4
91
+ pnpm self:export:likec4
92
+ pnpm docs:dev
93
+ ```
94
+
95
+ ## CLI
96
+
97
+ Build the repository, then invoke the same executable surface intended for
98
+ published use:
99
+
100
+ ```sh
101
+ pnpm build
102
+
103
+ node dist/cli.js init .
104
+ node dist/cli.js check .yarramate/workspace.yaml --json
105
+ node dist/cli.js compile .yarramate/workspace.yaml
106
+ node dist/cli.js context .yarramate/projections/context.yaml .yarramate/workspace.yaml
107
+ node dist/cli.js view .yarramate/projections/context.yaml .yarramate/workspace.yaml
108
+ node dist/cli.js evidence .yarramate/evidence/repository.yaml .yarramate/workspace.yaml
109
+ node dist/cli.js reconcile .yarramate/workspace.yaml
110
+ ```
111
+
112
+ `init` creates `.yarramate/architecture/main.yaml` and
113
+ `.yarramate/workspace.yaml`. Commands accept explicit source documents or one
114
+ explicit workspace manifest.
115
+
116
+ For a local consumer test, create a package artifact:
117
+
118
+ ```sh
119
+ pnpm pack --pack-destination /tmp/yarramate-package
120
+ npm install --global /tmp/yarramate-package/yarramate-0.1.0.tgz
121
+ yarramate --help
122
+ ```
123
+
124
+ See [Consuming YarraMate](docs/CONSUMING-YARRAMATE.md) for the packaged CLI,
125
+ schemas, agent skill, and optional adapters.
126
+
127
+ ## Library API
128
+
129
+ The typed library exposes the same deep compiler seam:
130
+
131
+ ```ts
132
+ import { compileWorkspace } from 'yarramate'
133
+
134
+ const result = compileWorkspace([
135
+ { path: 'architecture.yaml', source: yamlSource },
136
+ ])
137
+ ```
138
+
139
+ `compileWorkspaceWithProfileContext` additionally returns resolved profile
140
+ lineage for operations that explicitly require kind ancestry. Graph v2 remains
141
+ the stable, graph-only interchange result.
142
+
143
+ Normative schemas are available through package exports such as:
144
+
145
+ ```text
146
+ yarramate/schema/document
147
+ yarramate/schema/profile
148
+ yarramate/schema/workspace
149
+ yarramate/schema/graph-v2
150
+ yarramate/schema/projection
151
+ yarramate/schema/evidence
152
+ yarramate/schema/core-contract
153
+ ```
154
+
155
+ Optional adapter entry points are exported from:
156
+
157
+ ```text
158
+ yarramate/adapter/likec4
159
+ yarramate/adapter/graphify
160
+ ```
161
+
162
+ ## Contributing and security
163
+
164
+ Found a bug, confusing behaviour, missing capability, documentation problem,
165
+ or possible improvement? Please
166
+ [open a GitHub Issue](https://github.com/yarrasys/yarramate/issues/new).
167
+ Issues are welcome from users, contributors, agents, and curious observers.
168
+ You do not need to provide a solution, formal proposal, or implementation.
169
+
170
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before proposing changes to native
171
+ semantics or stable interfaces. Report suspected vulnerabilities according to
172
+ [SECURITY.md](SECURITY.md).
173
+
174
+ YarraMate is available under the [MIT License](LICENSE).
@@ -0,0 +1,40 @@
1
+ import type { Diagnostic, SemanticGraph, WorkspaceSource } from './compiler.js';
2
+ import { type SourceLocation } from './source-document.js';
3
+ export interface AdapterSubjectMapping {
4
+ readonly native: string;
5
+ readonly external: string;
6
+ readonly type: 'concept' | 'relationship';
7
+ }
8
+ export interface AdapterMapping {
9
+ readonly format: 'yarramate/adapter-mapping/v1';
10
+ readonly id: string;
11
+ readonly version: string;
12
+ readonly adapter: string;
13
+ readonly mappings: readonly AdapterSubjectMapping[];
14
+ }
15
+ export type AdapterMappingLoadResult = {
16
+ readonly ok: true;
17
+ readonly mapping: AdapterMapping;
18
+ } | {
19
+ readonly ok: false;
20
+ readonly diagnostics: readonly Diagnostic[];
21
+ };
22
+ export type AdapterMappingValidationResult = {
23
+ readonly ok: true;
24
+ readonly mapping: AdapterMapping;
25
+ } | {
26
+ readonly ok: false;
27
+ readonly diagnostics: readonly Diagnostic[];
28
+ };
29
+ export type AdapterMappingsValidationResult = {
30
+ readonly ok: true;
31
+ readonly mappings: readonly AdapterMapping[];
32
+ } | {
33
+ readonly ok: false;
34
+ readonly diagnostics: readonly Diagnostic[];
35
+ };
36
+ export declare function loadAdapterMapping(source: WorkspaceSource): AdapterMappingLoadResult;
37
+ export declare function adapterMappingLocation(mapping: AdapterMapping, field: 'id' | 'adapter'): SourceLocation;
38
+ export declare function adapterMappingEntryLocation(mapping: AdapterMapping, entry: AdapterSubjectMapping, field: 'native' | 'external' | 'type'): SourceLocation;
39
+ export declare function validateAdapterMapping(graph: SemanticGraph, mapping: AdapterMapping): AdapterMappingValidationResult;
40
+ export declare function validateAdapterMappings(graph: SemanticGraph, mappings: readonly AdapterMapping[]): AdapterMappingsValidationResult;
@@ -0,0 +1,200 @@
1
+ import Ajv2020Module from 'ajv/dist/2020.js';
2
+ import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from './source-document.js';
3
+ import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.json' with {
4
+ type: 'json'
5
+ };
6
+ const Ajv2020 = Ajv2020Module.default;
7
+ const validateSchema = new Ajv2020({ allErrors: true }).compile(adapterMappingSchema);
8
+ const mappingLocations = new WeakMap();
9
+ export function loadAdapterMapping(source) {
10
+ const loaded = loadSourceDocument(source, validateSchema, 'Adapter mapping');
11
+ if (!loaded.ok)
12
+ return loaded;
13
+ const { value, yaml, lineCounter } = loaded.document;
14
+ const mapping = {
15
+ ...value,
16
+ mappings: [...value.mappings].sort((left, right) => left.native.localeCompare(right.native) ||
17
+ left.external.localeCompare(right.external) ||
18
+ left.type.localeCompare(right.type)),
19
+ };
20
+ const locateNode = (yamlPath, pointer) => locateSourcePath(source.path, yaml, lineCounter, yamlPath, pointer);
21
+ mappingLocations.set(mapping, {
22
+ id: locateNode(['id'], '/id'),
23
+ adapter: locateNode(['adapter'], '/adapter'),
24
+ mappings: mapping.mappings.map((entry) => {
25
+ const authoredIndex = value.mappings.indexOf(entry);
26
+ const locate = (field) => locateNode(['mappings', authoredIndex, field], `/mappings/${authoredIndex}/${field}`);
27
+ return {
28
+ native: locate('native'),
29
+ external: locate('external'),
30
+ type: locate('type'),
31
+ };
32
+ }),
33
+ });
34
+ return { ok: true, mapping };
35
+ }
36
+ export function adapterMappingLocation(mapping, field) {
37
+ return (mappingLocations.get(mapping)?.[field] ?? {
38
+ path: `${mapping.id}.mapping.yaml`,
39
+ pointer: `/${field}`,
40
+ line: 1,
41
+ column: 1,
42
+ });
43
+ }
44
+ export function adapterMappingEntryLocation(mapping, entry, field) {
45
+ const index = mapping.mappings.indexOf(entry);
46
+ return (mappingLocations.get(mapping)?.mappings[index]?.[field] ?? {
47
+ path: `${mapping.id}.mapping.yaml`,
48
+ pointer: `/mappings/${Math.max(index, 0)}/${field}`,
49
+ line: 1,
50
+ column: 1,
51
+ });
52
+ }
53
+ export function validateAdapterMapping(graph, mapping) {
54
+ const subjectTypes = new Map(graph.subjects.map(({ id, type }) => [id, type]));
55
+ const locations = mappingLocations.get(mapping)?.mappings ?? [];
56
+ const diagnostics = [];
57
+ const seenNativeSubjects = new Set();
58
+ const seenExternalIdentities = new Set();
59
+ for (const [index, entry] of mapping.mappings.entries()) {
60
+ const fallback = (field) => ({
61
+ path: `${mapping.id}.mapping.yaml`,
62
+ pointer: `/mappings/${index}/${field}`,
63
+ line: 1,
64
+ column: 1,
65
+ });
66
+ const nativeLocation = locations[index]?.native ?? fallback('native');
67
+ const externalLocation = locations[index]?.external ?? fallback('external');
68
+ const typeLocation = locations[index]?.type ?? fallback('type');
69
+ const actualType = subjectTypes.get(entry.native);
70
+ if (actualType === undefined) {
71
+ diagnostics.push({
72
+ severity: 'error',
73
+ code: 'YM601',
74
+ message: `Native subject "${entry.native}" does not exist`,
75
+ path: nativeLocation.path,
76
+ pointer: nativeLocation.pointer,
77
+ line: nativeLocation.line,
78
+ column: nativeLocation.column,
79
+ });
80
+ }
81
+ else if (actualType !== entry.type) {
82
+ diagnostics.push({
83
+ severity: 'error',
84
+ code: 'YM602',
85
+ message: `Native subject "${entry.native}" is a ${actualType}, not a ${entry.type}`,
86
+ path: typeLocation.path,
87
+ pointer: typeLocation.pointer,
88
+ line: typeLocation.line,
89
+ column: typeLocation.column,
90
+ });
91
+ }
92
+ if (seenNativeSubjects.has(entry.native)) {
93
+ diagnostics.push({
94
+ severity: 'error',
95
+ code: 'YM603',
96
+ message: `Native subject "${entry.native}" is mapped more than once`,
97
+ path: nativeLocation.path,
98
+ pointer: nativeLocation.pointer,
99
+ line: nativeLocation.line,
100
+ column: nativeLocation.column,
101
+ });
102
+ }
103
+ seenNativeSubjects.add(entry.native);
104
+ if (seenExternalIdentities.has(entry.external)) {
105
+ diagnostics.push({
106
+ severity: 'error',
107
+ code: 'YM604',
108
+ message: `External identity "${entry.external}" is mapped more than once`,
109
+ path: externalLocation.path,
110
+ pointer: externalLocation.pointer,
111
+ line: externalLocation.line,
112
+ column: externalLocation.column,
113
+ });
114
+ }
115
+ seenExternalIdentities.add(entry.external);
116
+ }
117
+ return diagnostics.length === 0
118
+ ? { ok: true, mapping }
119
+ : { ok: false, diagnostics: diagnostics.sort(diagnosticOrder) };
120
+ }
121
+ export function validateAdapterMappings(graph, mappings) {
122
+ const ordered = [...mappings].sort((left, right) => {
123
+ const identityOrder = `${left.id}@${left.version}`.localeCompare(`${right.id}@${right.version}`);
124
+ if (identityOrder !== 0)
125
+ return identityOrder;
126
+ const leftPath = mappingLocations.get(left)?.id.path ?? '';
127
+ const rightPath = mappingLocations.get(right)?.id.path ?? '';
128
+ return leftPath.localeCompare(rightPath);
129
+ });
130
+ const diagnostics = [];
131
+ const seenIdentities = new Set();
132
+ const nativeOwnersByAdapter = new Map();
133
+ const externalOwnersByAdapter = new Map();
134
+ for (const mapping of ordered) {
135
+ const identity = `${mapping.id}@${mapping.version}`;
136
+ const validation = validateAdapterMapping(graph, mapping);
137
+ if (!validation.ok) {
138
+ diagnostics.push(...validation.diagnostics);
139
+ }
140
+ if (seenIdentities.has(identity)) {
141
+ const location = mappingLocations.get(mapping)?.id ?? {
142
+ path: `${mapping.id}.mapping.yaml`,
143
+ pointer: '/id',
144
+ line: 1,
145
+ column: 1,
146
+ };
147
+ diagnostics.push({
148
+ severity: 'error',
149
+ code: 'YM605',
150
+ message: `Adapter mapping "${identity}" is declared more than once`,
151
+ ...location,
152
+ });
153
+ }
154
+ seenIdentities.add(identity);
155
+ const nativeOwners = nativeOwnersByAdapter.get(mapping.adapter) ??
156
+ new Map();
157
+ const externalOwners = externalOwnersByAdapter.get(mapping.adapter) ??
158
+ new Map();
159
+ nativeOwnersByAdapter.set(mapping.adapter, nativeOwners);
160
+ externalOwnersByAdapter.set(mapping.adapter, externalOwners);
161
+ const locations = mappingLocations.get(mapping)?.mappings ?? [];
162
+ for (const [index, entry] of mapping.mappings.entries()) {
163
+ const nativeOwner = nativeOwners.get(entry.native);
164
+ if (nativeOwner !== undefined && nativeOwner !== mapping) {
165
+ const location = locations[index]?.native ?? {
166
+ path: `${mapping.id}.mapping.yaml`,
167
+ pointer: `/mappings/${index}/native`,
168
+ line: 1,
169
+ column: 1,
170
+ };
171
+ diagnostics.push({
172
+ severity: 'error',
173
+ code: 'YM603',
174
+ message: `Native subject "${entry.native}" is mapped more than once for adapter "${mapping.adapter}"`,
175
+ ...location,
176
+ });
177
+ }
178
+ nativeOwners.set(entry.native, nativeOwner ?? mapping);
179
+ const externalOwner = externalOwners.get(entry.external);
180
+ if (externalOwner !== undefined && externalOwner !== mapping) {
181
+ const location = locations[index]?.external ?? {
182
+ path: `${mapping.id}.mapping.yaml`,
183
+ pointer: `/mappings/${index}/external`,
184
+ line: 1,
185
+ column: 1,
186
+ };
187
+ diagnostics.push({
188
+ severity: 'error',
189
+ code: 'YM604',
190
+ message: `External identity "${entry.external}" is mapped more than once for adapter "${mapping.adapter}"`,
191
+ ...location,
192
+ });
193
+ }
194
+ externalOwners.set(entry.external, externalOwner ?? mapping);
195
+ }
196
+ }
197
+ return diagnostics.length === 0
198
+ ? { ok: true, mappings: ordered }
199
+ : { ok: false, diagnostics: diagnostics.sort(diagnosticOrder) };
200
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { type CliResult } from '../cli-support.js';
3
+ export declare function runGraphifyCli(args: readonly string[], cwd?: string): CliResult;
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { adapterMappingEntryLocation, adapterMappingLocation, loadAdapterMapping, validateAdapterMapping, } from '../adapter-mapping.js';
5
+ import { compileWorkspace } from '../compiler.js';
6
+ import { diagnosticJson, isMainModule, resolveCliWorkspaceSources, } from '../cli-support.js';
7
+ import { observeGraphify, } from './graphify.js';
8
+ const usage = 'Usage:\n' +
9
+ ' yarramate-graphify observe <graph.json> <mapping.yaml> <workspace-or-source...> --id <evidence-id> --version <major.minor>\n';
10
+ const parseOptions = (options) => {
11
+ const idFlag = options.indexOf('--id');
12
+ const versionFlag = options.indexOf('--version');
13
+ if (idFlag < 3 ||
14
+ versionFlag < 3 ||
15
+ options[idFlag + 1] === undefined ||
16
+ options[versionFlag + 1] === undefined)
17
+ return undefined;
18
+ const flagIndexes = [idFlag, versionFlag].sort((left, right) => left - right);
19
+ const firstFlag = flagIndexes[0];
20
+ if (firstFlag === undefined)
21
+ return undefined;
22
+ const paths = options.slice(0, firstFlag);
23
+ const trailing = options.slice(firstFlag);
24
+ if (paths.length < 3 ||
25
+ trailing.length !== 4 ||
26
+ new Set([trailing[0], trailing[2]]).size !== 2)
27
+ return undefined;
28
+ return {
29
+ graphPath: paths[0],
30
+ mappingPath: paths[1],
31
+ sources: paths.slice(2),
32
+ id: options[idFlag + 1],
33
+ version: options[versionFlag + 1],
34
+ };
35
+ };
36
+ export function runGraphifyCli(args, cwd = process.cwd()) {
37
+ const [command, ...options] = args;
38
+ const parsed = command === 'observe' ? parseOptions(options) : undefined;
39
+ if (parsed === undefined ||
40
+ !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(parsed.id) ||
41
+ !/^[0-9]+\.[0-9]+$/.test(parsed.version)) {
42
+ return { exitCode: 2, stdout: '', stderr: usage };
43
+ }
44
+ try {
45
+ const resolved = resolveCliWorkspaceSources(parsed.sources, cwd);
46
+ if (!resolved.ok) {
47
+ return {
48
+ exitCode: 1,
49
+ stdout: diagnosticJson(resolved.diagnostics),
50
+ stderr: '',
51
+ };
52
+ }
53
+ const compilation = compileWorkspace(resolved.paths.map((path) => ({
54
+ path,
55
+ source: readFileSync(resolve(cwd, path), 'utf8'),
56
+ })));
57
+ if (!compilation.ok) {
58
+ return {
59
+ exitCode: 1,
60
+ stdout: diagnosticJson(compilation.diagnostics),
61
+ stderr: '',
62
+ };
63
+ }
64
+ const loadedMapping = loadAdapterMapping({
65
+ path: parsed.mappingPath,
66
+ source: readFileSync(resolve(cwd, parsed.mappingPath), 'utf8'),
67
+ });
68
+ if (!loadedMapping.ok) {
69
+ return {
70
+ exitCode: 1,
71
+ stdout: diagnosticJson(loadedMapping.diagnostics),
72
+ stderr: '',
73
+ };
74
+ }
75
+ const validatedMapping = validateAdapterMapping(compilation.graph, loadedMapping.mapping);
76
+ if (!validatedMapping.ok) {
77
+ return {
78
+ exitCode: 1,
79
+ stdout: diagnosticJson(validatedMapping.diagnostics),
80
+ stderr: '',
81
+ };
82
+ }
83
+ const graph = JSON.parse(readFileSync(resolve(cwd, parsed.graphPath), 'utf8'));
84
+ if (!Array.isArray(graph.nodes) ||
85
+ graph.nodes.some((node) => typeof node !== 'object' ||
86
+ node === null ||
87
+ typeof node.id !== 'string' ||
88
+ node.id.length === 0)) {
89
+ return {
90
+ exitCode: 2,
91
+ stdout: '',
92
+ stderr: `${parsed.graphPath} does not contain Graphify nodes with string IDs\n`,
93
+ };
94
+ }
95
+ const observation = observeGraphify(graph, validatedMapping.mapping, { id: parsed.id, version: parsed.version });
96
+ if (!observation.ok) {
97
+ const diagnostics = observation.issues.map((issue) => ({
98
+ severity: 'error',
99
+ code: issue.code,
100
+ message: issue.message,
101
+ ...(issue.mapping === undefined
102
+ ? adapterMappingLocation(loadedMapping.mapping, 'adapter')
103
+ : adapterMappingEntryLocation(loadedMapping.mapping, issue.mapping, 'type')),
104
+ }));
105
+ return {
106
+ exitCode: 1,
107
+ stdout: diagnosticJson(diagnostics),
108
+ stderr: '',
109
+ };
110
+ }
111
+ return {
112
+ exitCode: 0,
113
+ stdout: `${JSON.stringify(observation.evidence, null, 2)}\n`,
114
+ stderr: '',
115
+ };
116
+ }
117
+ catch (error) {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ return { exitCode: 2, stdout: '', stderr: `${message}\n` };
120
+ }
121
+ }
122
+ if (isMainModule(import.meta.url, process.argv[1])) {
123
+ const result = runGraphifyCli(process.argv.slice(2));
124
+ process.stdout.write(result.stdout);
125
+ process.stderr.write(result.stderr);
126
+ process.exitCode = result.exitCode;
127
+ }
@@ -0,0 +1 @@
1
+ export { observeGraphify, type GraphifyGraph, type GraphifyObservationIssue, type GraphifyObservationResult, } from './graphify.js';
@@ -0,0 +1 @@
1
+ export { observeGraphify, } from './graphify.js';
@@ -0,0 +1,23 @@
1
+ import type { AdapterMapping, AdapterSubjectMapping } from '../adapter-mapping.js';
2
+ import type { EvidenceDocument } from '../evidence.js';
3
+ export interface GraphifyGraph {
4
+ readonly nodes: readonly {
5
+ readonly id: string;
6
+ }[];
7
+ }
8
+ export interface GraphifyObservationIssue {
9
+ readonly code: 'YMG101' | 'YMG102';
10
+ readonly message: string;
11
+ readonly mapping?: AdapterSubjectMapping;
12
+ }
13
+ export type GraphifyObservationResult = {
14
+ readonly ok: true;
15
+ readonly evidence: EvidenceDocument;
16
+ } | {
17
+ readonly ok: false;
18
+ readonly issues: readonly GraphifyObservationIssue[];
19
+ };
20
+ export declare function observeGraphify(graph: GraphifyGraph, mapping: AdapterMapping, identity: {
21
+ readonly id: string;
22
+ readonly version: string;
23
+ }): GraphifyObservationResult;
@@ -0,0 +1,47 @@
1
+ export function observeGraphify(graph, mapping, identity) {
2
+ if (mapping.adapter !== 'graphify') {
3
+ return {
4
+ ok: false,
5
+ issues: [{
6
+ code: 'YMG101',
7
+ message: `Adapter mapping "${mapping.id}@${mapping.version}" belongs to "${mapping.adapter}", not "graphify"`,
8
+ }],
9
+ };
10
+ }
11
+ const relationshipMappings = mapping.mappings.filter(({ type }) => type === 'relationship');
12
+ if (relationshipMappings.length > 0) {
13
+ return {
14
+ ok: false,
15
+ issues: relationshipMappings.map((entry) => ({
16
+ code: 'YMG102',
17
+ message: `Graphify node evidence cannot evaluate relationship subject "${entry.native}"`,
18
+ mapping: entry,
19
+ })),
20
+ };
21
+ }
22
+ const nodeIds = new Set(graph.nodes.map(({ id }) => id));
23
+ const observations = mapping.mappings.map((entry) => nodeIds.has(entry.external)
24
+ ? {
25
+ subject: entry.native,
26
+ result: 'confirmed',
27
+ evidence: { uri: `graphify:${entry.external}` },
28
+ }
29
+ : {
30
+ subject: entry.native,
31
+ result: 'not-observed',
32
+ evidence: {
33
+ uri: `graphify:${entry.external}`,
34
+ message: `Graphify node "${entry.external}" was not observed`,
35
+ },
36
+ });
37
+ return {
38
+ ok: true,
39
+ evidence: {
40
+ format: 'yarramate/evidence/v1',
41
+ id: identity.id,
42
+ version: identity.version,
43
+ provider: 'graphify',
44
+ observations,
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { type CliResult } from '../cli-support.js';
3
+ export declare function runLikeC4Cli(args: readonly string[], cwd?: string): CliResult;