yarramate 0.6.0 → 0.7.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 +39 -12
- package/catalogues/core-enrichment.yaml +700 -0
- package/dist/adapters/mcp-cli.js +55 -49
- package/dist/apply-command.d.ts +2 -0
- package/dist/apply-command.js +199 -0
- package/dist/{new-command.d.ts → ask-command.d.ts} +1 -1
- package/dist/ask-command.js +729 -0
- package/dist/brief.d.ts +3 -0
- package/dist/brief.js +235 -0
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/cli.js +22 -590
- package/dist/compiler.js +13 -0
- package/dist/core-contract.d.ts +1 -1
- package/dist/{status-command.d.ts → design-command.d.ts} +1 -1
- package/dist/design-command.js +218 -0
- package/dist/export-command.d.ts +2 -0
- package/dist/export-command.js +228 -0
- package/dist/interrogate-command.d.ts +104 -0
- package/dist/interrogate-command.js +268 -0
- package/dist/next-command.d.ts +5 -8
- package/dist/next-command.js +106 -211
- package/docs/CONSUMING-YARRAMATE.md +28 -21
- package/package.json +13 -11
- package/schema/yarramate-apply-result.schema.json +32 -0
- package/schema/yarramate-ask-result.schema.json +391 -0
- package/schema/yarramate-core-contract.schema.json +5 -11
- package/schema/yarramate-design-step.schema.json +152 -0
- package/schema/yarramate-document.schema.json +76 -9
- package/schema/yarramate-interrogation-report.schema.json +89 -0
- package/schema/yarramate-operations.schema.json +303 -0
- package/schema/yarramate-question-catalogue.schema.json +431 -0
- package/skills/yarramate-architecture/SKILL.md +42 -17
- package/skills/yarramate-architecture/references/native-authoring.md +73 -17
- package/dist/new-command.js +0 -111
- package/dist/status-command.js +0 -174
- package/schema/yarramate-next-result.schema.json +0 -85
- package/schema/yarramate-status-result.schema.json +0 -278
package/dist/adapters/mcp-cli.js
CHANGED
|
@@ -10,14 +10,65 @@ const workspaceProperty = {
|
|
|
10
10
|
};
|
|
11
11
|
const tools = [
|
|
12
12
|
{
|
|
13
|
-
name: '
|
|
14
|
-
description: '
|
|
13
|
+
name: 'yarramate_ask',
|
|
14
|
+
description: 'The consumed-now read surface. Without a query: orientation — check verdict, drift summary, open-question count, and the backlog in dependency order. With a query: free text matches concept ids, names, and descriptions and returns the connected slice; exact subject ids (document-id#local-id) and projection paths address precisely. Set mode for the roster (subjects), declarable vocabulary (kinds), build order (next), or the full open-questions report (open).',
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: 'object',
|
|
17
17
|
required: ['workspace'],
|
|
18
|
-
properties:
|
|
18
|
+
properties: {
|
|
19
|
+
...workspaceProperty,
|
|
20
|
+
query: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Free text, a globally qualified subject id, or a projection path',
|
|
23
|
+
},
|
|
24
|
+
mode: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: ['subjects', 'kinds', 'next', 'open'],
|
|
27
|
+
description: 'Optional flag mode instead of a query: the filterable roster, the declarable kind vocabulary, dependency-ordered planned work, or the open-questions report',
|
|
28
|
+
},
|
|
29
|
+
budget: {
|
|
30
|
+
type: 'integer',
|
|
31
|
+
minimum: 1,
|
|
32
|
+
description: 'Approximate token budget for the compact slice rendering (query form only)',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
arguments: (input) => {
|
|
37
|
+
const workspace = String(input.workspace);
|
|
38
|
+
if (typeof input.mode === 'string') {
|
|
39
|
+
return ['ask', workspace, `--${input.mode}`, '--json'];
|
|
40
|
+
}
|
|
41
|
+
if (typeof input.query === 'string' && input.query.length > 0) {
|
|
42
|
+
const budget = typeof input.budget === 'number'
|
|
43
|
+
? ['--budget', String(input.budget)]
|
|
44
|
+
: ['--json'];
|
|
45
|
+
return ['ask', workspace, input.query, ...budget];
|
|
46
|
+
}
|
|
47
|
+
return ['ask', workspace, '--json'];
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'yarramate_design',
|
|
52
|
+
description: 'The design interview, one stateless step: the top open question with its subject slice, materiality, and progress. Read-only — answers land through the CLI apply command in the repository, not through this server.',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
required: ['workspace'],
|
|
56
|
+
properties: {
|
|
57
|
+
...workspaceProperty,
|
|
58
|
+
subject: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Optional globally qualified subject id to narrow the interview',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
19
63
|
},
|
|
20
|
-
arguments: (input) => [
|
|
64
|
+
arguments: (input) => [
|
|
65
|
+
'design',
|
|
66
|
+
String(input.workspace),
|
|
67
|
+
...(typeof input.subject === 'string'
|
|
68
|
+
? ['--subject', input.subject]
|
|
69
|
+
: []),
|
|
70
|
+
'--json',
|
|
71
|
+
],
|
|
21
72
|
},
|
|
22
73
|
{
|
|
23
74
|
name: 'yarramate_check',
|
|
@@ -39,51 +90,6 @@ const tools = [
|
|
|
39
90
|
},
|
|
40
91
|
arguments: (input) => ['reconcile', String(input.workspace)],
|
|
41
92
|
},
|
|
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
93
|
];
|
|
88
94
|
const respond = (id, result) => {
|
|
89
95
|
process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result })}\n`);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { isSeq, parseDocument } from 'yaml';
|
|
4
|
+
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
5
|
+
import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
|
|
6
|
+
import { compileWorkspace } from './compiler.js';
|
|
7
|
+
import { loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
8
|
+
import { loadWorkspaceManifest } from './workspace.js';
|
|
9
|
+
import operationsSchema from '../schema/yarramate-operations.schema.json' with {
|
|
10
|
+
type: 'json'
|
|
11
|
+
};
|
|
12
|
+
const Ajv2020 = Ajv2020Module.default;
|
|
13
|
+
const validateOperations = new Ajv2020({ allErrors: true }).compile(operationsSchema);
|
|
14
|
+
// Scalar fields replace; list fields append. An answer enriches what is
|
|
15
|
+
// there — it never silently shrinks it (removals stay Git edits).
|
|
16
|
+
const SCALAR_CONCEPT_FIELDS = ['kind', 'name', 'description', 'status', 'owner'];
|
|
17
|
+
const LIST_CONCEPT_FIELDS = ['constraints', 'references', 'presentIn', 'attestations'];
|
|
18
|
+
const SCALAR_RELATIONSHIP_FIELDS = ['kind', 'from', 'to', 'name', 'description', 'status', 'mode', 'content'];
|
|
19
|
+
const LIST_RELATIONSHIP_FIELDS = ['references', 'presentIn'];
|
|
20
|
+
const appendBlockItem = (document, collection, item) => {
|
|
21
|
+
document.addIn([collection], item);
|
|
22
|
+
const sequence = document.getIn([collection], true);
|
|
23
|
+
if (isSeq(sequence)) {
|
|
24
|
+
sequence.flow = false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const findItem = (document, collection, id) => {
|
|
28
|
+
const sequence = document.get(collection, true);
|
|
29
|
+
if (!isSeq(sequence))
|
|
30
|
+
return undefined;
|
|
31
|
+
return sequence.items.find((item) => typeof item === 'object' &&
|
|
32
|
+
item !== null &&
|
|
33
|
+
'get' in item &&
|
|
34
|
+
item.get('id') === id);
|
|
35
|
+
};
|
|
36
|
+
const applyFields = (document, item, fields, scalars, lists) => {
|
|
37
|
+
for (const key of scalars) {
|
|
38
|
+
if (fields[key] !== undefined)
|
|
39
|
+
item.set(key, fields[key]);
|
|
40
|
+
}
|
|
41
|
+
for (const key of lists) {
|
|
42
|
+
const additions = fields[key];
|
|
43
|
+
if (additions === undefined || additions.length === 0)
|
|
44
|
+
continue;
|
|
45
|
+
const existing = item.get(key);
|
|
46
|
+
if (existing === undefined) {
|
|
47
|
+
item.set(key, additions);
|
|
48
|
+
}
|
|
49
|
+
else if (isSeq(existing)) {
|
|
50
|
+
for (const entry of additions) {
|
|
51
|
+
existing.items.push(document.createNode(entry));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
export function runApplyCommand(options, cwd) {
|
|
57
|
+
const json = options.includes('--json');
|
|
58
|
+
const rest = options.filter((option) => option !== '--json');
|
|
59
|
+
const [operationsPath, workspacePath] = rest;
|
|
60
|
+
if (rest.length !== 2 ||
|
|
61
|
+
operationsPath === undefined ||
|
|
62
|
+
workspacePath === undefined ||
|
|
63
|
+
rest.some((option) => option.startsWith('-'))) {
|
|
64
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const manifestSource = readFileSync(resolve(cwd, workspacePath), 'utf8');
|
|
68
|
+
if (parseDocument(manifestSource).get('format') !== 'yarramate/workspace/v1') {
|
|
69
|
+
return {
|
|
70
|
+
exitCode: 2,
|
|
71
|
+
stdout: '',
|
|
72
|
+
stderr: 'apply requires an explicit workspace manifest (yarramate/workspace/v1)\n',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const failed = (diagnostics) => ({
|
|
76
|
+
exitCode: 1,
|
|
77
|
+
stdout: json ? diagnosticJson(diagnostics) : humanDiagnostics(diagnostics),
|
|
78
|
+
stderr: '',
|
|
79
|
+
});
|
|
80
|
+
const loadedWorkspace = loadWorkspaceManifest({ path: workspacePath, source: manifestSource }, cwd);
|
|
81
|
+
if (!loadedWorkspace.ok)
|
|
82
|
+
return failed(loadedWorkspace.diagnostics);
|
|
83
|
+
const workspace = loadedWorkspace.workspace;
|
|
84
|
+
const operationsSource = readFileSync(resolve(cwd, operationsPath), 'utf8');
|
|
85
|
+
const loadedOperations = loadSourceDocument({ path: operationsPath, source: operationsSource }, validateOperations, 'Operations');
|
|
86
|
+
if (!loadedOperations.ok)
|
|
87
|
+
return failed(loadedOperations.diagnostics);
|
|
88
|
+
const operations = loadedOperations.document.value.operations;
|
|
89
|
+
const yaml = loadedOperations.document.yaml;
|
|
90
|
+
const lineCounter = loadedOperations.document.lineCounter;
|
|
91
|
+
// Documents are addressed by their manifest paths; an operation aimed
|
|
92
|
+
// anywhere else is rejected before anything is touched.
|
|
93
|
+
const workspaceDocuments = new Map(workspace.documents.map((path) => [resolve(cwd, path), path]));
|
|
94
|
+
const parsed = new Map();
|
|
95
|
+
const counts = {
|
|
96
|
+
addedConcepts: 0,
|
|
97
|
+
addedRelationships: 0,
|
|
98
|
+
updatedConcepts: 0,
|
|
99
|
+
updatedRelationships: 0,
|
|
100
|
+
};
|
|
101
|
+
for (const [index, operation] of operations.entries()) {
|
|
102
|
+
const absolute = resolve(cwd, operation.document);
|
|
103
|
+
const manifestPath = workspaceDocuments.get(absolute);
|
|
104
|
+
const locate = (message) => ({
|
|
105
|
+
severity: 'error',
|
|
106
|
+
code: 'YM912',
|
|
107
|
+
message,
|
|
108
|
+
...locateSourcePath(operationsPath, yaml, lineCounter, ['operations', index, 'document'], `/operations/${index}/document`),
|
|
109
|
+
});
|
|
110
|
+
if (manifestPath === undefined) {
|
|
111
|
+
return failed([
|
|
112
|
+
locate(`Operation ${index} targets "${operation.document}", which is not a document of workspace "${workspace.id}"`),
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
let document = parsed.get(absolute);
|
|
116
|
+
if (document === undefined) {
|
|
117
|
+
document = parseDocument(readFileSync(absolute, 'utf8'));
|
|
118
|
+
parsed.set(absolute, document);
|
|
119
|
+
}
|
|
120
|
+
if (operation.op === 'add-concept') {
|
|
121
|
+
appendBlockItem(document, 'concepts', operation.concept);
|
|
122
|
+
counts.addedConcepts += 1;
|
|
123
|
+
}
|
|
124
|
+
else if (operation.op === 'add-relationship') {
|
|
125
|
+
appendBlockItem(document, 'relationships', operation.relationship);
|
|
126
|
+
counts.addedRelationships += 1;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const collection = operation.op === 'update-concept' ? 'concepts' : 'relationships';
|
|
130
|
+
const payload = operation.op === 'update-concept'
|
|
131
|
+
? operation.concept
|
|
132
|
+
: operation.relationship;
|
|
133
|
+
const item = findItem(document, collection, payload.id);
|
|
134
|
+
if (item === undefined) {
|
|
135
|
+
return failed([
|
|
136
|
+
locate(`Operation ${index} updates "${payload.id}", which does not exist in ${operation.document}`),
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
applyFields(document, item, payload, operation.op === 'update-concept'
|
|
140
|
+
? SCALAR_CONCEPT_FIELDS
|
|
141
|
+
: SCALAR_RELATIONSHIP_FIELDS, operation.op === 'update-concept'
|
|
142
|
+
? LIST_CONCEPT_FIELDS
|
|
143
|
+
: LIST_RELATIONSHIP_FIELDS);
|
|
144
|
+
if (operation.op === 'update-concept') {
|
|
145
|
+
counts.updatedConcepts += 1;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
counts.updatedRelationships += 1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// The atomic gate: the whole candidate workspace must compile before a
|
|
153
|
+
// single byte is written; any diagnostic rejects the entire batch.
|
|
154
|
+
const candidates = new Map([...parsed.entries()].map(([absolute, document]) => [
|
|
155
|
+
absolute,
|
|
156
|
+
document.toString({ lineWidth: 0 }),
|
|
157
|
+
]));
|
|
158
|
+
const compilation = compileWorkspace([...workspace.profiles, ...workspace.documents].map((path) => {
|
|
159
|
+
const absolute = resolve(cwd, path);
|
|
160
|
+
return {
|
|
161
|
+
path,
|
|
162
|
+
source: candidates.get(absolute) ?? readFileSync(absolute, 'utf8'),
|
|
163
|
+
};
|
|
164
|
+
}));
|
|
165
|
+
if (!compilation.ok)
|
|
166
|
+
return failed(compilation.diagnostics);
|
|
167
|
+
for (const [absolute, source] of candidates) {
|
|
168
|
+
writeFileSync(absolute, source, 'utf8');
|
|
169
|
+
}
|
|
170
|
+
const touched = [...parsed.keys()]
|
|
171
|
+
.map((absolute) => workspaceDocuments.get(absolute))
|
|
172
|
+
.sort();
|
|
173
|
+
if (json) {
|
|
174
|
+
return {
|
|
175
|
+
exitCode: 0,
|
|
176
|
+
stdout: `${JSON.stringify({
|
|
177
|
+
format: 'yarramate/apply-result/v1',
|
|
178
|
+
workspace: workspace.id,
|
|
179
|
+
applied: counts,
|
|
180
|
+
documents: touched,
|
|
181
|
+
}, null, 2)}\n`,
|
|
182
|
+
stderr: '',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const applied = counts.addedConcepts +
|
|
186
|
+
counts.addedRelationships +
|
|
187
|
+
counts.updatedConcepts +
|
|
188
|
+
counts.updatedRelationships;
|
|
189
|
+
return {
|
|
190
|
+
exitCode: 0,
|
|
191
|
+
stdout: `Applied ${applied} operation${applied === 1 ? '' : 's'} to ${touched.join(', ')}\n`,
|
|
192
|
+
stderr: '',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
197
|
+
return { exitCode: 2, stdout: '', stderr: `${message}\n` };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type CliResult } from './cli-support.js';
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function runAskCommand(options: readonly string[], cwd: string): CliResult;
|