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.
Files changed (38) hide show
  1. package/README.md +39 -12
  2. package/catalogues/core-enrichment.yaml +700 -0
  3. package/dist/adapters/mcp-cli.js +55 -49
  4. package/dist/apply-command.d.ts +2 -0
  5. package/dist/apply-command.js +199 -0
  6. package/dist/{new-command.d.ts → ask-command.d.ts} +1 -1
  7. package/dist/ask-command.js +729 -0
  8. package/dist/brief.d.ts +3 -0
  9. package/dist/brief.js +235 -0
  10. package/dist/cli-support.d.ts +1 -1
  11. package/dist/cli-support.js +1 -1
  12. package/dist/cli.js +22 -590
  13. package/dist/compiler.js +13 -0
  14. package/dist/core-contract.d.ts +1 -1
  15. package/dist/{status-command.d.ts → design-command.d.ts} +1 -1
  16. package/dist/design-command.js +218 -0
  17. package/dist/export-command.d.ts +2 -0
  18. package/dist/export-command.js +228 -0
  19. package/dist/interrogate-command.d.ts +104 -0
  20. package/dist/interrogate-command.js +268 -0
  21. package/dist/next-command.d.ts +5 -8
  22. package/dist/next-command.js +106 -211
  23. package/docs/CONSUMING-YARRAMATE.md +28 -21
  24. package/package.json +13 -11
  25. package/schema/yarramate-apply-result.schema.json +32 -0
  26. package/schema/yarramate-ask-result.schema.json +391 -0
  27. package/schema/yarramate-core-contract.schema.json +5 -11
  28. package/schema/yarramate-design-step.schema.json +152 -0
  29. package/schema/yarramate-document.schema.json +76 -9
  30. package/schema/yarramate-interrogation-report.schema.json +89 -0
  31. package/schema/yarramate-operations.schema.json +303 -0
  32. package/schema/yarramate-question-catalogue.schema.json +431 -0
  33. package/skills/yarramate-architecture/SKILL.md +42 -17
  34. package/skills/yarramate-architecture/references/native-authoring.md +73 -17
  35. package/dist/new-command.js +0 -111
  36. package/dist/status-command.js +0 -174
  37. package/schema/yarramate-next-result.schema.json +0 -85
  38. package/schema/yarramate-status-result.schema.json +0 -278
@@ -0,0 +1,3 @@
1
+ import type { ResolvedProfileContext } from './compiler.js';
2
+ import type { ProjectionResult } from './projection.js';
3
+ export declare function renderBrief(result: ProjectionResult, profileContext?: ResolvedProfileContext, budgetTokens?: number): string;
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
+ }
@@ -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 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] [--strict]\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 next <projection.yaml> <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";
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;
@@ -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 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] [--strict]\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 next <projection.yaml> <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';
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,