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/brief.d.ts
ADDED
package/dist/brief.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { conceptKinds } from './profile.js';
|
|
2
|
+
const coreKindNames = new Map(conceptKinds.map(({ id, name }) => [id, name]));
|
|
3
|
+
const motivationKindIds = new Set(conceptKinds
|
|
4
|
+
.filter(({ layer }) => layer === 'motivation')
|
|
5
|
+
.map(({ id }) => id));
|
|
6
|
+
const claimValue = (claims, subject, predicate) => {
|
|
7
|
+
const object = claims.find((claim) => claim.subject === subject && claim.predicate === predicate)?.object;
|
|
8
|
+
return object !== undefined && 'value' in object ? object.value : undefined;
|
|
9
|
+
};
|
|
10
|
+
const claimReferences = (claims, subject, predicate) => claims.flatMap((claim) => claim.subject === subject &&
|
|
11
|
+
claim.predicate === predicate &&
|
|
12
|
+
'ref' in claim.object
|
|
13
|
+
? [claim.object.ref]
|
|
14
|
+
: []);
|
|
15
|
+
// Resolve a qualified kind to its nearest core-profile local id through
|
|
16
|
+
// declared lineage, mirroring how `next` orients relationships (ADR 0048).
|
|
17
|
+
const coreLocalKind = (kind, lineages) => {
|
|
18
|
+
for (const candidate of [kind, ...(lineages?.get(kind) ?? [])]) {
|
|
19
|
+
const separator = candidate.indexOf('#');
|
|
20
|
+
if (separator !== -1 && candidate.startsWith('yarramate/core@')) {
|
|
21
|
+
return candidate.slice(separator + 1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
};
|
|
26
|
+
const humanizeKind = (kind) => {
|
|
27
|
+
const local = kind.slice(kind.indexOf('#') + 1);
|
|
28
|
+
return local
|
|
29
|
+
.replaceAll(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
30
|
+
.replaceAll('-', ' ')
|
|
31
|
+
.toLowerCase();
|
|
32
|
+
};
|
|
33
|
+
const article = (reading) => /^[aeiou]/.test(reading) ? 'an' : 'a';
|
|
34
|
+
const sentenceEnd = (text) => /[.!?]["')\]]*$/.test(text.trimEnd()) ? text.trimEnd() : `${text.trimEnd()}.`;
|
|
35
|
+
const listPhrase = (items) => items.length <= 1
|
|
36
|
+
? (items[0] ?? '')
|
|
37
|
+
: `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
|
|
38
|
+
// Phrase forms are the prose readings of each core relationship kind's
|
|
39
|
+
// declared intent — the same table `next` reads for ordering, spoken
|
|
40
|
+
// from the source's perspective.
|
|
41
|
+
const relationshipPhrase = (coreKind, fallbackKind, mode, content) => {
|
|
42
|
+
switch (coreKind) {
|
|
43
|
+
case 'serving':
|
|
44
|
+
return 'serves';
|
|
45
|
+
case 'access':
|
|
46
|
+
return mode === 'read'
|
|
47
|
+
? 'reads'
|
|
48
|
+
: mode === 'write'
|
|
49
|
+
? 'writes'
|
|
50
|
+
: mode === 'read-write'
|
|
51
|
+
? 'reads and writes'
|
|
52
|
+
: 'accesses';
|
|
53
|
+
case 'realization':
|
|
54
|
+
return 'realizes';
|
|
55
|
+
case 'composition':
|
|
56
|
+
return 'comprises';
|
|
57
|
+
case 'aggregation':
|
|
58
|
+
return 'aggregates';
|
|
59
|
+
case 'assignment':
|
|
60
|
+
return 'is assigned to';
|
|
61
|
+
case 'triggering':
|
|
62
|
+
return 'triggers';
|
|
63
|
+
case 'flow':
|
|
64
|
+
return content === undefined ? 'flows to' : `sends ${content} to`;
|
|
65
|
+
case 'specialization':
|
|
66
|
+
return 'specializes';
|
|
67
|
+
case 'influence':
|
|
68
|
+
return 'influences';
|
|
69
|
+
case 'association':
|
|
70
|
+
return 'is associated with';
|
|
71
|
+
default:
|
|
72
|
+
return humanizeKind(fallbackKind);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
76
|
+
export function renderBrief(result, profileContext, budgetTokens) {
|
|
77
|
+
const stateIds = new Set(result.claims
|
|
78
|
+
.filter(({ predicate }) => predicate === 'yarramate/state/type')
|
|
79
|
+
.map(({ subject }) => subject));
|
|
80
|
+
const concepts = result.subjects.filter(({ id, type }) => type === 'concept' && !stateIds.has(id));
|
|
81
|
+
const nameOf = (id) => claimValue(result.claims, id, 'yarramate/concept/name') ?? id;
|
|
82
|
+
const kindOf = (id) => claimValue(result.claims, id, 'yarramate/concept/kind');
|
|
83
|
+
const statusOf = (id) => claimValue(result.claims, id, 'yarramate/lifecycle/status');
|
|
84
|
+
const descriptionOf = (id) => claimValue(result.claims, id, 'yarramate/concept/description');
|
|
85
|
+
const kindReading = (kind) => {
|
|
86
|
+
const core = coreLocalKind(kind, profileContext?.conceptKindLineages);
|
|
87
|
+
const coreName = core === undefined ? undefined : coreKindNames.get(core);
|
|
88
|
+
return kind.startsWith('yarramate/core@') && coreName !== undefined
|
|
89
|
+
? coreName.toLowerCase()
|
|
90
|
+
: humanizeKind(kind);
|
|
91
|
+
};
|
|
92
|
+
const isMotivation = (id) => {
|
|
93
|
+
const kind = kindOf(id);
|
|
94
|
+
if (kind === undefined)
|
|
95
|
+
return false;
|
|
96
|
+
const core = coreLocalKind(kind, profileContext?.conceptKindLineages);
|
|
97
|
+
return core !== undefined && motivationKindIds.has(core);
|
|
98
|
+
};
|
|
99
|
+
const outgoing = new Map();
|
|
100
|
+
for (const subject of result.subjects) {
|
|
101
|
+
if (subject.type !== 'relationship')
|
|
102
|
+
continue;
|
|
103
|
+
const claim = result.claims.find(({ id, object }) => id === subject.id && 'ref' in object);
|
|
104
|
+
if (claim === undefined || !('ref' in claim.object))
|
|
105
|
+
continue;
|
|
106
|
+
const description = claimValue(result.claims, subject.id, 'yarramate/relationship/description');
|
|
107
|
+
const entry = {
|
|
108
|
+
phrase: relationshipPhrase(coreLocalKind(claim.predicate, profileContext?.relationshipKindLineages), claim.predicate, claimValue(result.claims, subject.id, 'yarramate/access/mode'), claimValue(result.claims, subject.id, 'yarramate/flow/content')),
|
|
109
|
+
target: claim.object.ref,
|
|
110
|
+
...(description === undefined ? {} : { description }),
|
|
111
|
+
};
|
|
112
|
+
outgoing.set(claim.subject, [
|
|
113
|
+
...(outgoing.get(claim.subject) ?? []),
|
|
114
|
+
entry,
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
const relationshipSentences = (id) => {
|
|
118
|
+
const entries = outgoing.get(id) ?? [];
|
|
119
|
+
const grouped = new Map();
|
|
120
|
+
const sentences = [];
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const target = `"${nameOf(entry.target)}"`;
|
|
123
|
+
if (entry.description !== undefined) {
|
|
124
|
+
sentences.push(`It ${entry.phrase} ${target} (${entry.description}).`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
grouped.set(entry.phrase, [...(grouped.get(entry.phrase) ?? []), target]);
|
|
128
|
+
}
|
|
129
|
+
return [
|
|
130
|
+
...[...grouped].map(([phrase, targets]) => `It ${phrase} ${listPhrase(targets)}.`),
|
|
131
|
+
...sentences,
|
|
132
|
+
];
|
|
133
|
+
};
|
|
134
|
+
const supportSentences = (id) => {
|
|
135
|
+
const sentences = [];
|
|
136
|
+
const constraints = claimReferences(result.claims, id, 'yarramate/constraint/requires');
|
|
137
|
+
if (constraints.length > 0) {
|
|
138
|
+
sentences.push(`Constrained by ${listPhrase(constraints.map((constraint) => `"${nameOf(constraint)}"`))}.`);
|
|
139
|
+
}
|
|
140
|
+
const owner = claimReferences(result.claims, id, 'yarramate/ownership/owner')[0];
|
|
141
|
+
if (owner !== undefined) {
|
|
142
|
+
sentences.push(`Owned by "${nameOf(owner)}".`);
|
|
143
|
+
}
|
|
144
|
+
return sentences;
|
|
145
|
+
};
|
|
146
|
+
const motivationParagraph = (id) => {
|
|
147
|
+
const kind = kindOf(id) ?? '';
|
|
148
|
+
const reading = kindReading(kind);
|
|
149
|
+
const label = reading.charAt(0).toUpperCase() + reading.slice(1);
|
|
150
|
+
const description = descriptionOf(id);
|
|
151
|
+
const opening = description === undefined
|
|
152
|
+
? `${label} "${nameOf(id)}".`
|
|
153
|
+
: `${label} "${nameOf(id)}": "${description}"`;
|
|
154
|
+
return [sentenceEnd(opening), ...relationshipSentences(id), ...supportSentences(id)].join(' ');
|
|
155
|
+
};
|
|
156
|
+
const workParagraph = (id) => {
|
|
157
|
+
const kind = kindOf(id) ?? '';
|
|
158
|
+
const reading = kindReading(kind);
|
|
159
|
+
const status = statusOf(id);
|
|
160
|
+
const name = `"${nameOf(id)}"`;
|
|
161
|
+
const opening = status === 'planned'
|
|
162
|
+
? `You are building ${name}, ${article(reading)} ${reading}.`
|
|
163
|
+
: status === 'current'
|
|
164
|
+
? `${name}, ${article(reading)} ${reading}, already exists.`
|
|
165
|
+
: status === 'retired'
|
|
166
|
+
? `${name}, ${article(reading)} ${reading}, is retired.`
|
|
167
|
+
: `${name} is ${article(reading)} ${reading}.`;
|
|
168
|
+
const description = descriptionOf(id);
|
|
169
|
+
return [
|
|
170
|
+
opening,
|
|
171
|
+
...(description === undefined ? [] : [sentenceEnd(description)]),
|
|
172
|
+
...relationshipSentences(id),
|
|
173
|
+
...supportSentences(id),
|
|
174
|
+
].join(' ');
|
|
175
|
+
};
|
|
176
|
+
// Motivation opens the brief (the interview's waves lead with why), the
|
|
177
|
+
// planned work follows (it is what the reader came to do), and existing
|
|
178
|
+
// context comes after; the same order ranks paragraphs under a budget.
|
|
179
|
+
const statusRank = (id) => {
|
|
180
|
+
const status = statusOf(id);
|
|
181
|
+
return status === 'planned' ? 1 : status === 'retired' ? 3 : 2;
|
|
182
|
+
};
|
|
183
|
+
const motivation = concepts.filter(({ id }) => isMotivation(id));
|
|
184
|
+
const work = concepts.filter(({ id }) => !isMotivation(id));
|
|
185
|
+
const paragraphs = [
|
|
186
|
+
{
|
|
187
|
+
heading: '## Why this exists',
|
|
188
|
+
entries: motivation.map(({ id }) => ({
|
|
189
|
+
text: motivationParagraph(id),
|
|
190
|
+
rank: 0,
|
|
191
|
+
})),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
heading: '## The pieces',
|
|
195
|
+
entries: work
|
|
196
|
+
.map(({ id }) => ({ text: workParagraph(id), rank: statusRank(id) }))
|
|
197
|
+
.sort((a, b) => a.rank - b.rank),
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
const title = result.presentation?.title ?? result.projection;
|
|
201
|
+
const header = [`# ${title}`];
|
|
202
|
+
if (result.presentation?.description !== undefined) {
|
|
203
|
+
header.push('', result.presentation.description);
|
|
204
|
+
}
|
|
205
|
+
const budget = budgetTokens ?? Number.POSITIVE_INFINITY;
|
|
206
|
+
let spent = estimateTokens(header.join('\n'));
|
|
207
|
+
let omitted = 0;
|
|
208
|
+
let total = 0;
|
|
209
|
+
const sections = [];
|
|
210
|
+
for (const section of paragraphs) {
|
|
211
|
+
total += section.entries.length;
|
|
212
|
+
if (section.entries.length === 0)
|
|
213
|
+
continue;
|
|
214
|
+
const kept = [];
|
|
215
|
+
let sectionSpent = estimateTokens(section.heading);
|
|
216
|
+
for (const entry of section.entries) {
|
|
217
|
+
const cost = estimateTokens(entry.text);
|
|
218
|
+
if (spent + sectionSpent + cost > budget) {
|
|
219
|
+
omitted += 1;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
kept.push(entry.text);
|
|
223
|
+
sectionSpent += cost;
|
|
224
|
+
}
|
|
225
|
+
if (kept.length > 0) {
|
|
226
|
+
sections.push('', section.heading, '', kept.join('\n\n'));
|
|
227
|
+
spent += sectionSpent;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const lines = [...header, ...sections];
|
|
231
|
+
if (omitted > 0) {
|
|
232
|
+
lines.push('', `[budget ${budgetTokens}: ${omitted} of ${total} paragraphs omitted — raise --budget or use --json for the complete slice]`);
|
|
233
|
+
}
|
|
234
|
+
return `${lines.join('\n')}\n`;
|
|
235
|
+
}
|
package/dist/cli-support.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface CliResult {
|
|
|
7
7
|
export declare const isMainModule: (moduleUrl: string, entrypoint: string | undefined) => boolean;
|
|
8
8
|
export declare const packageVersion: string;
|
|
9
9
|
export declare const versionResult: (binary: string) => CliResult;
|
|
10
|
-
export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate
|
|
10
|
+
export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml>\n";
|
|
11
11
|
export declare const diagnosticJson: (diagnostics: unknown) => string;
|
|
12
12
|
export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counted?: {
|
|
13
13
|
readonly documents: number;
|
package/dist/cli-support.js
CHANGED
|
@@ -23,7 +23,7 @@ export const versionResult = (binary) => ({
|
|
|
23
23
|
stdout: `${binary} ${packageVersion}\n`,
|
|
24
24
|
stderr: '',
|
|
25
25
|
});
|
|
26
|
-
export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate
|
|
26
|
+
export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml>\n';
|
|
27
27
|
export const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
28
28
|
format: 'yarramate/diagnostic-result/v1',
|
|
29
29
|
diagnostics,
|