yarramate 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/adapters/graphify-cli.js +4 -1
- package/dist/adapters/likec4-cli.js +39 -4
- package/dist/adapters/mcp-cli.d.ts +9 -0
- package/dist/adapters/mcp-cli.js +172 -0
- package/dist/cli-support.d.ts +3 -1
- package/dist/cli-support.js +10 -1
- package/dist/cli.js +179 -10
- package/dist/compiler.js +17 -5
- package/dist/index.d.ts +1 -1
- package/dist/new-command.d.ts +2 -0
- package/dist/new-command.js +111 -0
- package/dist/profile.d.ts +5 -0
- package/dist/profile.js +4 -0
- package/dist/projection.d.ts +1 -0
- package/dist/projection.js +106 -0
- package/dist/reconciliation.d.ts +9 -1
- package/dist/reconciliation.js +35 -4
- package/dist/source-document.d.ts +3 -1
- package/dist/source-document.js +43 -1
- package/dist/status-command.d.ts +2 -0
- package/dist/status-command.js +171 -0
- package/docs/CONSUMING-YARRAMATE.md +48 -0
- package/package.json +8 -6
- package/schema/yarramate-core-contract.schema.json +11 -2
- package/schema/yarramate-likec4-project.schema.json +1 -1
- package/schema/yarramate-reconciliation-report.schema.json +16 -0
- package/schema/yarramate-status-result.schema.json +273 -0
- package/skills/yarramate-architecture/SKILL.md +18 -9
- package/skills/yarramate-architecture/references/native-authoring.md +74 -6
package/README.md
CHANGED
|
@@ -106,6 +106,7 @@ npm install --global yarramate
|
|
|
106
106
|
yarramate --help
|
|
107
107
|
|
|
108
108
|
npx yarramate check .yarramate/workspace.yaml
|
|
109
|
+
npx yarramate status .yarramate/workspace.yaml --json
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
When developing the repository, build and invoke the same executable surface:
|
|
@@ -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) =>
|
|
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(
|
|
534
|
+
source: readFileSync(resolve(projectDirectory, path), 'utf8'),
|
|
500
535
|
};
|
|
501
536
|
referencedSources.set(path, source);
|
|
502
537
|
return source;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface JsonRpcRequest {
|
|
3
|
+
readonly jsonrpc: '2.0';
|
|
4
|
+
readonly id?: number | string | null;
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly params?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export declare const handleRequest: (request: JsonRpcRequest) => void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { isMainModule, packageVersion, versionResult, } from '../cli-support.js';
|
|
4
|
+
import { runCli } from '../cli.js';
|
|
5
|
+
const workspaceProperty = {
|
|
6
|
+
workspace: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
description: 'Path to the explicit workspace manifest, for example .yarramate/workspace.yaml',
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const tools = [
|
|
12
|
+
{
|
|
13
|
+
name: 'yarramate_status',
|
|
14
|
+
description: 'One-call workspace orientation: check verdict, reconciliation summary, and a titled inventory of documents, states, projections, evidence, and contracts.',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
required: ['workspace'],
|
|
18
|
+
properties: workspaceProperty,
|
|
19
|
+
},
|
|
20
|
+
arguments: (input) => ['status', String(input.workspace), '--json'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'yarramate_check',
|
|
24
|
+
description: 'Deterministic correctness check of a workspace; returns the machine-readable check result. Never a quality or completeness judgement.',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
required: ['workspace'],
|
|
28
|
+
properties: workspaceProperty,
|
|
29
|
+
},
|
|
30
|
+
arguments: (input) => ['check', String(input.workspace), '--json'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'yarramate_reconcile',
|
|
34
|
+
description: 'Compare declared architecture with evaluated evidence; returns the reconciliation report with contradicted, unknown, and not-observed findings.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
required: ['workspace'],
|
|
38
|
+
properties: workspaceProperty,
|
|
39
|
+
},
|
|
40
|
+
arguments: (input) => ['reconcile', String(input.workspace)],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'yarramate_context',
|
|
44
|
+
description: 'Bounded architecture context. Provide either a projection path or one or more globally qualified subjects (document-id#local-id) for an ad-hoc connected neighbourhood. Optional token budget switches to a compact ranked rendering.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
required: ['workspace'],
|
|
48
|
+
properties: {
|
|
49
|
+
...workspaceProperty,
|
|
50
|
+
projection: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Path to an authored projection definition',
|
|
53
|
+
},
|
|
54
|
+
subjects: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
items: { type: 'string' },
|
|
57
|
+
description: 'Globally qualified subject identities for ad-hoc context',
|
|
58
|
+
},
|
|
59
|
+
budget: {
|
|
60
|
+
type: 'integer',
|
|
61
|
+
minimum: 1,
|
|
62
|
+
description: 'Approximate token budget for the compact rendering',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
arguments: (input) => {
|
|
67
|
+
const budget = typeof input.budget === 'number'
|
|
68
|
+
? ['--budget', String(input.budget)]
|
|
69
|
+
: [];
|
|
70
|
+
if (typeof input.projection === 'string') {
|
|
71
|
+
return [
|
|
72
|
+
'context',
|
|
73
|
+
input.projection,
|
|
74
|
+
String(input.workspace),
|
|
75
|
+
...budget,
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
const subjects = Array.isArray(input.subjects)
|
|
79
|
+
? input.subjects.flatMap((subject) => [
|
|
80
|
+
'--subject',
|
|
81
|
+
String(subject),
|
|
82
|
+
])
|
|
83
|
+
: [];
|
|
84
|
+
return ['context', ...subjects, String(input.workspace), ...budget];
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
const respond = (id, result) => {
|
|
89
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result })}\n`);
|
|
90
|
+
};
|
|
91
|
+
const respondError = (id, code, message) => {
|
|
92
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } })}\n`);
|
|
93
|
+
};
|
|
94
|
+
export const handleRequest = (request) => {
|
|
95
|
+
const id = request.id ?? null;
|
|
96
|
+
if (request.method === 'initialize') {
|
|
97
|
+
respond(id, {
|
|
98
|
+
protocolVersion: '2025-06-18',
|
|
99
|
+
capabilities: { tools: {} },
|
|
100
|
+
serverInfo: { name: 'yarramate', version: packageVersion },
|
|
101
|
+
instructions: 'Read-only architecture context for YarraMate workspaces. The native documents in the repository remain canonical; this server never mutates them.',
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (request.method === 'tools/list') {
|
|
106
|
+
respond(id, {
|
|
107
|
+
tools: tools.map(({ name, description, inputSchema }) => ({
|
|
108
|
+
name,
|
|
109
|
+
description,
|
|
110
|
+
inputSchema,
|
|
111
|
+
})),
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (request.method === 'tools/call') {
|
|
116
|
+
const params = request.params ?? {};
|
|
117
|
+
const name = typeof params.name === 'string' ? params.name : '';
|
|
118
|
+
const tool = tools.find((candidate) => candidate.name === name);
|
|
119
|
+
if (tool === undefined) {
|
|
120
|
+
respondError(id, -32602, `Unknown tool "${name}"`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const input = typeof params.arguments === 'object' && params.arguments !== null
|
|
124
|
+
? params.arguments
|
|
125
|
+
: {};
|
|
126
|
+
const result = runCli([...tool.arguments(input)], process.cwd());
|
|
127
|
+
respond(id, {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: 'text',
|
|
131
|
+
text: result.exitCode === 0
|
|
132
|
+
? result.stdout
|
|
133
|
+
: result.stdout || result.stderr,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: result.exitCode !== 0,
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (request.id !== undefined) {
|
|
141
|
+
respondError(id, -32601, `Method "${request.method}" not found`);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
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
|
+
}
|
|
172
|
+
}
|
package/dist/cli-support.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/cli-support.js
CHANGED
|
@@ -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
|
|
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,17 +5,45 @@ 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
|
+
import { runNewCommand } from './new-command.js';
|
|
11
|
+
import { runStatusCommand } from './status-command.js';
|
|
10
12
|
import { evaluateEvidence, evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
11
13
|
import { reconcileEvidenceReports } from './reconciliation.js';
|
|
12
14
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
13
|
-
import { evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
15
|
+
import { evaluateProjection, loadProjection, renderBudgetedContext, renderProjectionMarkdown, } from './projection.js';
|
|
16
|
+
const extractBudget = (options) => {
|
|
17
|
+
const rest = [];
|
|
18
|
+
let budget;
|
|
19
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
20
|
+
const option = options[index];
|
|
21
|
+
if (option === '--budget') {
|
|
22
|
+
const value = options[index + 1];
|
|
23
|
+
if (value === undefined ||
|
|
24
|
+
budget !== undefined ||
|
|
25
|
+
!/^[1-9][0-9]*$/.test(value)) {
|
|
26
|
+
return { ok: false };
|
|
27
|
+
}
|
|
28
|
+
budget = Number(value);
|
|
29
|
+
index += 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (option !== undefined)
|
|
33
|
+
rest.push(option);
|
|
34
|
+
}
|
|
35
|
+
return budget === undefined ? { ok: true, rest } : { ok: true, budget, rest };
|
|
36
|
+
};
|
|
14
37
|
const runProjection = (options, cwd, output) => {
|
|
15
|
-
const
|
|
38
|
+
const extracted = output === 'json' ? extractBudget(options) : { ok: true, rest: options };
|
|
39
|
+
if (!extracted.ok) {
|
|
40
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
41
|
+
}
|
|
42
|
+
const budget = 'budget' in extracted ? extracted.budget : undefined;
|
|
43
|
+
const [projectionPath, ...paths] = extracted.rest;
|
|
16
44
|
if (projectionPath === undefined ||
|
|
17
45
|
paths.length === 0 ||
|
|
18
|
-
|
|
46
|
+
extracted.rest.some((option) => option.startsWith('-'))) {
|
|
19
47
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
20
48
|
}
|
|
21
49
|
try {
|
|
@@ -53,7 +81,9 @@ const runProjection = (options, cwd, output) => {
|
|
|
53
81
|
return {
|
|
54
82
|
exitCode: 0,
|
|
55
83
|
stdout: output === 'json'
|
|
56
|
-
?
|
|
84
|
+
? budget === undefined
|
|
85
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
86
|
+
: renderBudgetedContext(result, budget)
|
|
57
87
|
: renderProjectionMarkdown(result),
|
|
58
88
|
stderr: '',
|
|
59
89
|
};
|
|
@@ -63,6 +93,95 @@ const runProjection = (options, cwd, output) => {
|
|
|
63
93
|
return { exitCode: 2, stdout: '', stderr: `${message}\n` };
|
|
64
94
|
}
|
|
65
95
|
};
|
|
96
|
+
const runAdHocContext = (options, cwd) => {
|
|
97
|
+
const extracted = extractBudget(options);
|
|
98
|
+
if (!extracted.ok) {
|
|
99
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
100
|
+
}
|
|
101
|
+
const budget = 'budget' in extracted ? extracted.budget : undefined;
|
|
102
|
+
const subjects = [];
|
|
103
|
+
const paths = [];
|
|
104
|
+
for (let index = 0; index < extracted.rest.length; index += 1) {
|
|
105
|
+
const option = extracted.rest[index];
|
|
106
|
+
if (option === '--subject') {
|
|
107
|
+
const value = extracted.rest[index + 1];
|
|
108
|
+
if (value === undefined || value.startsWith('-')) {
|
|
109
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
110
|
+
}
|
|
111
|
+
subjects.push(value);
|
|
112
|
+
index += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (option === undefined || option.startsWith('-')) {
|
|
116
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
117
|
+
}
|
|
118
|
+
paths.push(option);
|
|
119
|
+
}
|
|
120
|
+
if (subjects.length === 0 || paths.length === 0) {
|
|
121
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
122
|
+
}
|
|
123
|
+
const unqualified = subjects.filter((subject) => !subject.includes('#'));
|
|
124
|
+
if (unqualified.length > 0) {
|
|
125
|
+
return {
|
|
126
|
+
exitCode: 2,
|
|
127
|
+
stdout: '',
|
|
128
|
+
stderr: 'Ad-hoc context subjects must be globally qualified as ' +
|
|
129
|
+
`"<document-id>#<local-id>": ${unqualified.join(', ')}\n`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const resolved = resolveCliWorkspaceSources(paths, cwd);
|
|
134
|
+
if (!resolved.ok) {
|
|
135
|
+
return {
|
|
136
|
+
exitCode: 1,
|
|
137
|
+
stdout: diagnosticJson(resolved.diagnostics),
|
|
138
|
+
stderr: '',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const compilation = compileWorkspaceWithProfileContext(resolved.paths.map((path) => ({
|
|
142
|
+
path,
|
|
143
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
144
|
+
})));
|
|
145
|
+
if (!compilation.ok) {
|
|
146
|
+
return {
|
|
147
|
+
exitCode: 1,
|
|
148
|
+
stdout: diagnosticJson(compilation.diagnostics),
|
|
149
|
+
stderr: '',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const known = new Set(compilation.graph.subjects.map(({ id }) => id));
|
|
153
|
+
const unknown = subjects.filter((subject) => !known.has(subject));
|
|
154
|
+
if (unknown.length > 0) {
|
|
155
|
+
return {
|
|
156
|
+
exitCode: 1,
|
|
157
|
+
stdout: '',
|
|
158
|
+
stderr: `Unknown subject ${unknown.length === 1 ? 'identity' : 'identities'}: ` +
|
|
159
|
+
`${unknown.join(', ')} (the compiled workspace declares ${known.size} subjects)\n`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const result = evaluateProjection(compilation.graph, {
|
|
163
|
+
format: 'yarramate/projection/v1',
|
|
164
|
+
id: 'ad-hoc-context',
|
|
165
|
+
version: '0.0',
|
|
166
|
+
query: { subjects, relationships: 'connected' },
|
|
167
|
+
presentation: {
|
|
168
|
+
title: 'Ad-hoc context',
|
|
169
|
+
description: `Connected neighbourhood of ${subjects.join(', ')}`,
|
|
170
|
+
},
|
|
171
|
+
}, compilation.profileContext);
|
|
172
|
+
return {
|
|
173
|
+
exitCode: 0,
|
|
174
|
+
stdout: budget === undefined
|
|
175
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
176
|
+
: renderBudgetedContext(result, budget),
|
|
177
|
+
stderr: '',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
return { exitCode: 2, stdout: '', stderr: `${message}\n` };
|
|
183
|
+
}
|
|
184
|
+
};
|
|
66
185
|
const runEvidence = (options, cwd) => {
|
|
67
186
|
const [evidencePath, ...paths] = options;
|
|
68
187
|
if (evidencePath === undefined ||
|
|
@@ -178,7 +297,7 @@ const runReconciliation = (options, cwd) => {
|
|
|
178
297
|
}
|
|
179
298
|
return {
|
|
180
299
|
exitCode: 0,
|
|
181
|
-
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`,
|
|
182
301
|
stderr: '',
|
|
183
302
|
};
|
|
184
303
|
}
|
|
@@ -234,8 +353,10 @@ const runStateComparison = (options, cwd) => {
|
|
|
234
353
|
}
|
|
235
354
|
};
|
|
236
355
|
const runInit = (options, cwd) => {
|
|
237
|
-
const
|
|
238
|
-
|
|
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 ||
|
|
239
360
|
target === undefined ||
|
|
240
361
|
target.startsWith('-')) {
|
|
241
362
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
@@ -270,9 +391,46 @@ const runInit = (options, cwd) => {
|
|
|
270
391
|
'projections: []\n' +
|
|
271
392
|
'adapterMappings: []\n' +
|
|
272
393
|
'evidence: []\n', 'utf8');
|
|
394
|
+
const pointerMarker = '## YarraMate architecture';
|
|
395
|
+
const pointerBlock = `${pointerMarker}\n` +
|
|
396
|
+
'\n' +
|
|
397
|
+
'This repository declares its architecture as canonical, versioned\n' +
|
|
398
|
+
'YarraMate documents in `.yarramate/`. When prose documentation and the\n' +
|
|
399
|
+
'model disagree, the model is authoritative.\n' +
|
|
400
|
+
'\n' +
|
|
401
|
+
'- Orient first: `yarramate status .yarramate/workspace.yaml --json`\n' +
|
|
402
|
+
'- Validate changes: `yarramate check .yarramate/workspace.yaml --json`\n' +
|
|
403
|
+
'- Bounded task context: `yarramate context <projection.yaml> .yarramate/workspace.yaml`\n' +
|
|
404
|
+
'\n' +
|
|
405
|
+
'Author native documents only; never edit generated output.\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
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
273
430
|
return {
|
|
274
431
|
exitCode: 0,
|
|
275
|
-
stdout: `Created ${displayPath} and ${displayManifestPath}\n
|
|
432
|
+
stdout: `Created ${displayPath} and ${displayManifestPath}\n` +
|
|
433
|
+
pointerNotes.join(''),
|
|
276
434
|
stderr: '',
|
|
277
435
|
};
|
|
278
436
|
};
|
|
@@ -558,6 +716,9 @@ export function runCli(args, cwd = process.cwd()) {
|
|
|
558
716
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
559
717
|
return { exitCode: 0, stdout: usage, stderr: '' };
|
|
560
718
|
}
|
|
719
|
+
if (command === '--version' || command === '-v') {
|
|
720
|
+
return versionResult('yarramate');
|
|
721
|
+
}
|
|
561
722
|
if (command === 'init') {
|
|
562
723
|
return runInit(options, cwd);
|
|
563
724
|
}
|
|
@@ -571,7 +732,9 @@ export function runCli(args, cwd = process.cwd()) {
|
|
|
571
732
|
return runCompile(options, cwd);
|
|
572
733
|
}
|
|
573
734
|
if (command === 'context') {
|
|
574
|
-
return
|
|
735
|
+
return options.includes('--subject')
|
|
736
|
+
? runAdHocContext(options, cwd)
|
|
737
|
+
: runProjection(options, cwd, 'json');
|
|
575
738
|
}
|
|
576
739
|
if (command === 'view') {
|
|
577
740
|
return runProjection(options, cwd, 'markdown');
|
|
@@ -588,6 +751,12 @@ export function runCli(args, cwd = process.cwd()) {
|
|
|
588
751
|
if (command === 'check') {
|
|
589
752
|
return runCheckCommand(options, cwd);
|
|
590
753
|
}
|
|
754
|
+
if (command === 'status') {
|
|
755
|
+
return runStatusCommand(options, cwd);
|
|
756
|
+
}
|
|
757
|
+
if (command === 'new') {
|
|
758
|
+
return runNewCommand(options, cwd);
|
|
759
|
+
}
|
|
591
760
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
592
761
|
}
|
|
593
762
|
if (isMainModule(import.meta.url, process.argv[1])) {
|