yarramate 0.3.3 → 0.4.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/mcp-cli.d.ts +9 -0
- package/dist/adapters/mcp-cli.js +165 -0
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/cli.js +162 -6
- package/dist/compiler.js +15 -4
- package/dist/new-command.d.ts +2 -0
- package/dist/new-command.js +111 -0
- package/dist/projection.d.ts +1 -0
- package/dist/projection.js +106 -0
- 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 +4 -2
- package/schema/yarramate-core-contract.schema.json +11 -2
- package/schema/yarramate-status-result.schema.json +273 -0
- package/skills/yarramate-architecture/SKILL.md +9 -0
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:
|
|
@@ -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,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { isMainModule } 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: '0.1' },
|
|
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
|
+
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
|
+
});
|
|
165
|
+
}
|
package/dist/cli-support.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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 compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...]\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 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";
|
|
9
9
|
export declare const diagnosticJson: (diagnostics: unknown) => string;
|
|
10
10
|
export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counted?: {
|
|
11
11
|
readonly documents: number;
|
package/dist/cli-support.js
CHANGED
|
@@ -14,7 +14,7 @@ export const isMainModule = (moduleUrl, entrypoint) => {
|
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
16
|
};
|
|
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 compile <source.yaml> [source.yaml ...]\n yarramate context <projection.yaml> <source.yaml> [source.yaml ...]\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';
|
|
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';
|
|
18
18
|
export const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
19
19
|
format: 'yarramate/diagnostic-result/v1',
|
|
20
20
|
diagnostics,
|
package/dist/cli.js
CHANGED
|
@@ -7,15 +7,43 @@ import { compareArchitectureStates } from './architecture-state.js';
|
|
|
7
7
|
import { serializeSemanticGraph } from './graph.js';
|
|
8
8
|
import { diagnosticJson, humanDiagnostics, isMainModule, resolveCliWorkspaceSources, usage, } 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 ||
|
|
@@ -270,9 +389,38 @@ const runInit = (options, cwd) => {
|
|
|
270
389
|
'projections: []\n' +
|
|
271
390
|
'adapterMappings: []\n' +
|
|
272
391
|
'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` +
|
|
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
|
+
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`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
273
421
|
return {
|
|
274
422
|
exitCode: 0,
|
|
275
|
-
stdout: `Created ${displayPath} and ${displayManifestPath}\n
|
|
423
|
+
stdout: `Created ${displayPath} and ${displayManifestPath}\n` + agentsNote,
|
|
276
424
|
stderr: '',
|
|
277
425
|
};
|
|
278
426
|
};
|
|
@@ -571,7 +719,9 @@ export function runCli(args, cwd = process.cwd()) {
|
|
|
571
719
|
return runCompile(options, cwd);
|
|
572
720
|
}
|
|
573
721
|
if (command === 'context') {
|
|
574
|
-
return
|
|
722
|
+
return options.includes('--subject')
|
|
723
|
+
? runAdHocContext(options, cwd)
|
|
724
|
+
: runProjection(options, cwd, 'json');
|
|
575
725
|
}
|
|
576
726
|
if (command === 'view') {
|
|
577
727
|
return runProjection(options, cwd, 'markdown');
|
|
@@ -588,6 +738,12 @@ export function runCli(args, cwd = process.cwd()) {
|
|
|
588
738
|
if (command === 'check') {
|
|
589
739
|
return runCheckCommand(options, cwd);
|
|
590
740
|
}
|
|
741
|
+
if (command === 'status') {
|
|
742
|
+
return runStatusCommand(options, cwd);
|
|
743
|
+
}
|
|
744
|
+
if (command === 'new') {
|
|
745
|
+
return runNewCommand(options, cwd);
|
|
746
|
+
}
|
|
591
747
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
592
748
|
}
|
|
593
749
|
if (isMainModule(import.meta.url, process.argv[1])) {
|
package/dist/compiler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { LineCounter, parseDocument } from 'yaml';
|
|
3
3
|
import { conceptKinds, relationshipPolicies, } from './profile.js';
|
|
4
|
+
import { closestCandidate, describeSchemaViolation, } from './source-document.js';
|
|
4
5
|
import documentSchema from '../schema/yarramate-document.schema.json' with {
|
|
5
6
|
type: 'json'
|
|
6
7
|
};
|
|
@@ -112,7 +113,7 @@ function compileWorkspaceResolved(sources) {
|
|
|
112
113
|
code: 'YM201',
|
|
113
114
|
message: property
|
|
114
115
|
? `Property "${property}" is not allowed`
|
|
115
|
-
: `Profile schema violation: ${error
|
|
116
|
+
: `Profile schema violation: ${describeSchemaViolation(error)}`,
|
|
116
117
|
path: input.path,
|
|
117
118
|
pointer,
|
|
118
119
|
line: position.line,
|
|
@@ -335,7 +336,7 @@ function compileWorkspaceResolved(sources) {
|
|
|
335
336
|
code: 'YM201',
|
|
336
337
|
message: property
|
|
337
338
|
? `Property "${property}" is not allowed`
|
|
338
|
-
: `Document schema violation: ${error
|
|
339
|
+
: `Document schema violation: ${describeSchemaViolation(error)}`,
|
|
339
340
|
path: input.path,
|
|
340
341
|
pointer,
|
|
341
342
|
line: source.line,
|
|
@@ -536,7 +537,12 @@ function compileWorkspaceResolved(sources) {
|
|
|
536
537
|
diagnostics.push({
|
|
537
538
|
severity: 'error',
|
|
538
539
|
code: 'YM401',
|
|
539
|
-
message: `Unknown concept kind "${concept.kind}" in profile "${value.profile}"
|
|
540
|
+
message: `Unknown concept kind "${concept.kind}" in profile "${value.profile}"${(() => {
|
|
541
|
+
const suggestion = closestCandidate(concept.kind, selectedProfile.conceptKinds.keys());
|
|
542
|
+
return suggestion === undefined
|
|
543
|
+
? ''
|
|
544
|
+
: `; did you mean "${suggestion}"?`;
|
|
545
|
+
})()}`,
|
|
540
546
|
path: input.path,
|
|
541
547
|
pointer,
|
|
542
548
|
line: source.line,
|
|
@@ -742,7 +748,12 @@ function compileWorkspaceResolved(sources) {
|
|
|
742
748
|
diagnostics.push({
|
|
743
749
|
severity: 'error',
|
|
744
750
|
code: 'YM402',
|
|
745
|
-
message: `Unknown relationship kind "${relationship.kind}" in profile "${value.profile}"
|
|
751
|
+
message: `Unknown relationship kind "${relationship.kind}" in profile "${value.profile}"${(() => {
|
|
752
|
+
const suggestion = closestCandidate(relationship.kind, selectedProfile.relationshipKinds.keys());
|
|
753
|
+
return suggestion === undefined
|
|
754
|
+
? ''
|
|
755
|
+
: `; did you mean "${suggestion}"?`;
|
|
756
|
+
})()}`,
|
|
746
757
|
path: input.path,
|
|
747
758
|
pointer,
|
|
748
759
|
line: source.line,
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { humanDiagnostics, usage } from './cli-support.js';
|
|
4
|
+
import { loadProjection } from './projection.js';
|
|
5
|
+
const singleValueFlags = new Set([
|
|
6
|
+
'--id',
|
|
7
|
+
'--version',
|
|
8
|
+
'--title',
|
|
9
|
+
'--description',
|
|
10
|
+
'--relationships',
|
|
11
|
+
]);
|
|
12
|
+
const repeatableFlags = new Set(['--document', '--subject', '--kind']);
|
|
13
|
+
const yamlText = (value) => /^[A-Za-z0-9][A-Za-z0-9 ._/@#-]*$/.test(value)
|
|
14
|
+
? value
|
|
15
|
+
: JSON.stringify(value);
|
|
16
|
+
export function runNewCommand(options, cwd) {
|
|
17
|
+
const [family, target, ...flags] = options;
|
|
18
|
+
if (family !== 'projection' || target === undefined || target.startsWith('-')) {
|
|
19
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
20
|
+
}
|
|
21
|
+
const single = new Map();
|
|
22
|
+
const repeated = new Map();
|
|
23
|
+
for (let index = 0; index < flags.length; index += 2) {
|
|
24
|
+
const flag = flags[index];
|
|
25
|
+
const value = flags[index + 1];
|
|
26
|
+
if (flag === undefined ||
|
|
27
|
+
value === undefined ||
|
|
28
|
+
value.startsWith('--') ||
|
|
29
|
+
(!singleValueFlags.has(flag) && !repeatableFlags.has(flag))) {
|
|
30
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
31
|
+
}
|
|
32
|
+
if (singleValueFlags.has(flag)) {
|
|
33
|
+
if (single.has(flag)) {
|
|
34
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
35
|
+
}
|
|
36
|
+
single.set(flag, value);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
repeated.set(flag, [...(repeated.get(flag) ?? []), value]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const id = single.get('--id');
|
|
43
|
+
if (id === undefined) {
|
|
44
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
45
|
+
}
|
|
46
|
+
const documents = repeated.get('--document');
|
|
47
|
+
const subjects = repeated.get('--subject');
|
|
48
|
+
const kinds = repeated.get('--kind');
|
|
49
|
+
if (documents === undefined && subjects === undefined && kinds === undefined) {
|
|
50
|
+
return {
|
|
51
|
+
exitCode: 2,
|
|
52
|
+
stdout: '',
|
|
53
|
+
stderr: 'new projection requires at least one selector: --document, --subject, or --kind\n',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const targetPath = resolve(cwd, target);
|
|
57
|
+
if (existsSync(targetPath)) {
|
|
58
|
+
return {
|
|
59
|
+
exitCode: 2,
|
|
60
|
+
stdout: '',
|
|
61
|
+
stderr: `${target} already exists; nothing was changed\n`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const lines = [
|
|
65
|
+
'format: yarramate/projection/v1',
|
|
66
|
+
`id: ${yamlText(id)}`,
|
|
67
|
+
`version: "${single.get('--version') ?? '1.0'}"`,
|
|
68
|
+
'query:',
|
|
69
|
+
];
|
|
70
|
+
const list = (key, values) => {
|
|
71
|
+
if (values === undefined)
|
|
72
|
+
return;
|
|
73
|
+
lines.push(` ${key}:`);
|
|
74
|
+
for (const value of values)
|
|
75
|
+
lines.push(` - ${yamlText(value)}`);
|
|
76
|
+
};
|
|
77
|
+
list('subjects', subjects);
|
|
78
|
+
list('documents', documents);
|
|
79
|
+
list('kinds', kinds);
|
|
80
|
+
const relationships = single.get('--relationships');
|
|
81
|
+
if (relationships !== undefined) {
|
|
82
|
+
lines.push(` relationships: ${yamlText(relationships)}`);
|
|
83
|
+
}
|
|
84
|
+
const title = single.get('--title');
|
|
85
|
+
const description = single.get('--description');
|
|
86
|
+
if (title !== undefined || description !== undefined) {
|
|
87
|
+
lines.push('presentation:');
|
|
88
|
+
if (title !== undefined)
|
|
89
|
+
lines.push(` title: ${yamlText(title)}`);
|
|
90
|
+
if (description !== undefined) {
|
|
91
|
+
lines.push(` description: ${yamlText(description)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const source = `${lines.join('\n')}\n`;
|
|
95
|
+
const loaded = loadProjection({ path: target, source });
|
|
96
|
+
if (!loaded.ok) {
|
|
97
|
+
return {
|
|
98
|
+
exitCode: 1,
|
|
99
|
+
stdout: '',
|
|
100
|
+
stderr: humanDiagnostics(loaded.diagnostics),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
104
|
+
writeFileSync(targetPath, source, 'utf8');
|
|
105
|
+
return {
|
|
106
|
+
exitCode: 0,
|
|
107
|
+
stdout: `Created ${target} (${loaded.projection.id}@${loaded.projection.version})\n` +
|
|
108
|
+
'Include it in the workspace manifest projections list if no glob already covers it\n',
|
|
109
|
+
stderr: '',
|
|
110
|
+
};
|
|
111
|
+
}
|
package/dist/projection.d.ts
CHANGED
|
@@ -40,3 +40,4 @@ export type ProjectionLoadResult = {
|
|
|
40
40
|
export declare function loadProjection(source: WorkspaceSource): ProjectionLoadResult;
|
|
41
41
|
export declare function evaluateProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext): ProjectionResult;
|
|
42
42
|
export declare function renderProjectionMarkdown(result: ProjectionResult): string;
|
|
43
|
+
export declare function renderBudgetedContext(result: ProjectionResult, budgetTokens: number): string;
|